mirror of
				https://git.wownero.com/wownero/wownero.git
				synced 2024-08-15 01:03:23 +00:00 
			
		
		
		
	multisig key exchange update and refactor
This commit is contained in:
		
							parent
							
								
									b58a9fb12e
								
							
						
					
					
						commit
						e08abaa43f
					
				
					 30 changed files with 2224 additions and 952 deletions
				
			
		| 
						 | 
				
			
			@ -64,6 +64,11 @@ namespace crypto {
 | 
			
		|||
    friend class crypto_ops;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  POD_CLASS public_key_memsafe : epee::mlocked<tools::scrubbed<public_key>> {
 | 
			
		||||
    public_key_memsafe() = default;
 | 
			
		||||
    public_key_memsafe(const public_key &original) { memcpy(this->data, original.data, 32); }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>;
 | 
			
		||||
 | 
			
		||||
  POD_CLASS public_keyV {
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +105,7 @@ namespace crypto {
 | 
			
		|||
  void random32_unbiased(unsigned char *bytes);
 | 
			
		||||
 | 
			
		||||
  static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
 | 
			
		||||
    sizeof(public_key) == 32 && sizeof(secret_key) == 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");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -310,9 +315,13 @@ namespace crypto {
 | 
			
		|||
 | 
			
		||||
  const extern crypto::public_key null_pkey;
 | 
			
		||||
  const extern crypto::secret_key null_skey;
 | 
			
		||||
 | 
			
		||||
  inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; }
 | 
			
		||||
  inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CRYPTO_MAKE_HASHABLE(public_key)
 | 
			
		||||
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key)
 | 
			
		||||
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe)
 | 
			
		||||
CRYPTO_MAKE_HASHABLE(key_image)
 | 
			
		||||
CRYPTO_MAKE_COMPARABLE(signature)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -253,11 +253,6 @@ DISABLE_VS_WARNINGS(4244 4345)
 | 
			
		|||
    return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key);
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  void account_base::finalize_multisig(const crypto::public_key &spend_public_key)
 | 
			
		||||
  {
 | 
			
		||||
    m_keys.m_account_address.m_spend_public_key = spend_public_key;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  const account_keys& account_base::get_keys() const
 | 
			
		||||
  {
 | 
			
		||||
    return m_keys;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,7 +82,6 @@ namespace cryptonote
 | 
			
		|||
    void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey);
 | 
			
		||||
    void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey);
 | 
			
		||||
    bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys);
 | 
			
		||||
    void finalize_multisig(const crypto::public_key &spend_public_key);
 | 
			
		||||
    const account_keys& get_keys() const;
 | 
			
		||||
    std::string get_public_address_str(network_type nettype) const;
 | 
			
		||||
    std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -306,7 +306,26 @@ namespace cryptonote
 | 
			
		|||
    {
 | 
			
		||||
      // derive secret key with subaddress - step 1: original CN derivation
 | 
			
		||||
      crypto::secret_key scalar_step1;
 | 
			
		||||
      hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b
 | 
			
		||||
      crypto::secret_key spend_skey = crypto::null_skey;
 | 
			
		||||
 | 
			
		||||
      if (ack.m_multisig_keys.empty())
 | 
			
		||||
      {
 | 
			
		||||
        // if not multisig, use normal spend skey
 | 
			
		||||
        spend_skey = ack.m_spend_secret_key;
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        // if multisig, use sum of multisig privkeys (local account's share of aggregate spend key)
 | 
			
		||||
        for (const auto &multisig_key : ack.m_multisig_keys)
 | 
			
		||||
        {
 | 
			
		||||
          sc_add((unsigned char*)spend_skey.data,
 | 
			
		||||
            (const unsigned char*)multisig_key.data,
 | 
			
		||||
            (const unsigned char*)spend_skey.data);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // computes Hs(a*R || idx) + b
 | 
			
		||||
      hwdev.derive_secret_key(recv_derivation, real_output_index, spend_skey, scalar_step1);
 | 
			
		||||
 | 
			
		||||
      // step 2: add Hs(a || index_major || index_minor)
 | 
			
		||||
      crypto::secret_key subaddr_sk;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -229,6 +229,7 @@ namespace config
 | 
			
		|||
  const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58;
 | 
			
		||||
  const unsigned char HASH_KEY_MEMORY = 'k';
 | 
			
		||||
  const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 | 
			
		||||
  const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg";
 | 
			
		||||
  const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2";
 | 
			
		||||
  const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round";
 | 
			
		||||
  const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0";
 | 
			
		||||
| 
						 | 
				
			
			@ -236,6 +237,9 @@ namespace config
 | 
			
		|||
  const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature";
 | 
			
		||||
  const unsigned char HASH_KEY_MM_SLOT = 'm';
 | 
			
		||||
 | 
			
		||||
  // Multisig
 | 
			
		||||
  const uint32_t MULTISIG_MAX_SIGNERS{16};
 | 
			
		||||
 | 
			
		||||
  namespace testnet
 | 
			
		||||
  {
 | 
			
		||||
    uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,6 @@ using namespace epee;
 | 
			
		|||
#include "crypto/crypto.h"
 | 
			
		||||
#include "crypto/hash.h"
 | 
			
		||||
#include "ringct/rctSigs.h"
 | 
			
		||||
#include "multisig/multisig.h"
 | 
			
		||||
 | 
			
		||||
using namespace crypto;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,55 +95,35 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // gather the keys
 | 
			
		||||
    std::vector<crypto::secret_key> sk(total);
 | 
			
		||||
    std::vector<crypto::public_key> pk(total);
 | 
			
		||||
    std::vector<std::string> first_round_msgs;
 | 
			
		||||
    first_round_msgs.reserve(total);
 | 
			
		||||
    for (size_t n = 0; n < total; ++n)
 | 
			
		||||
    {
 | 
			
		||||
      wallets[n]->decrypt_keys(pwd_container->password());
 | 
			
		||||
      if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
 | 
			
		||||
      {
 | 
			
		||||
        tools::fail_msg_writer() << genms::tr("Failed to verify multisig info");
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      first_round_msgs.emplace_back(wallets[n]->get_multisig_first_kex_msg());
 | 
			
		||||
 | 
			
		||||
      wallets[n]->encrypt_keys(pwd_container->password());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // make the wallets multisig
 | 
			
		||||
    std::vector<std::string> extra_info(total);
 | 
			
		||||
    std::vector<std::string> kex_msgs_intermediate(total);
 | 
			
		||||
    std::stringstream ss;
 | 
			
		||||
    for (size_t n = 0; n < total; ++n)
 | 
			
		||||
    {
 | 
			
		||||
      std::string name = basename + "-" + std::to_string(n + 1);
 | 
			
		||||
      std::vector<crypto::secret_key> skn;
 | 
			
		||||
      std::vector<crypto::public_key> pkn;
 | 
			
		||||
      for (size_t k = 0; k < total; ++k)
 | 
			
		||||
      {
 | 
			
		||||
        if (k != n)
 | 
			
		||||
        {
 | 
			
		||||
          skn.push_back(sk[k]);
 | 
			
		||||
          pkn.push_back(pk[k]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold);
 | 
			
		||||
 | 
			
		||||
      kex_msgs_intermediate[n] = wallets[n]->make_multisig(pwd_container->password(), first_round_msgs, threshold);
 | 
			
		||||
 | 
			
		||||
      ss << "  " << name << std::endl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //exchange keys unless exchange_multisig_keys returns no extra info
 | 
			
		||||
    while (!extra_info[0].empty())
 | 
			
		||||
    while (!kex_msgs_intermediate[0].empty())
 | 
			
		||||
    {
 | 
			
		||||
      std::unordered_set<crypto::public_key> pkeys;
 | 
			
		||||
      std::vector<crypto::public_key> signers(total);
 | 
			
		||||
      for (size_t n = 0; n < total; ++n)
 | 
			
		||||
      {
 | 
			
		||||
        if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n]))
 | 
			
		||||
        {
 | 
			
		||||
          tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info");
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      for (size_t n = 0; n < total; ++n)
 | 
			
		||||
      {
 | 
			
		||||
          extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers);
 | 
			
		||||
          kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,12 +27,17 @@
 | 
			
		|||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
set(multisig_sources
 | 
			
		||||
  multisig.cpp)
 | 
			
		||||
  multisig.cpp
 | 
			
		||||
  multisig_account.cpp
 | 
			
		||||
  multisig_account_kex_impl.cpp
 | 
			
		||||
  multisig_kex_msg.cpp)
 | 
			
		||||
 | 
			
		||||
set(multisig_headers)
 | 
			
		||||
 | 
			
		||||
set(multisig_private_headers
 | 
			
		||||
  multisig.h)
 | 
			
		||||
  multisig.h
 | 
			
		||||
  multisig_account.h
 | 
			
		||||
  multisig_kex_msg.h)
 | 
			
		||||
 | 
			
		||||
monero_private_headers(multisig
 | 
			
		||||
  ${multisig_private_headers})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
// Copyright (c) 2017-2020, The Monero Project
 | 
			
		||||
// Copyright (c) 2017-2021, The Monero Project
 | 
			
		||||
// 
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
// 
 | 
			
		||||
| 
						 | 
				
			
			@ -26,29 +26,34 @@
 | 
			
		|||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include "include_base_utils.h"
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
#include "cryptonote_basic/account.h"
 | 
			
		||||
#include "cryptonote_basic/cryptonote_format_utils.h"
 | 
			
		||||
#include "multisig.h"
 | 
			
		||||
#include "cryptonote_config.h"
 | 
			
		||||
#include "include_base_utils.h"
 | 
			
		||||
#include "multisig.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#undef MONERO_DEFAULT_LOG_CATEGORY
 | 
			
		||||
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
namespace cryptonote
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(key != crypto::null_skey, "Unexpected null secret key (danger!).");
 | 
			
		||||
 | 
			
		||||
    rct::key multisig_salt;
 | 
			
		||||
    static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size");
 | 
			
		||||
    memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key));
 | 
			
		||||
 | 
			
		||||
    // private key = H(key, domain-sep)
 | 
			
		||||
    rct::keyV data;
 | 
			
		||||
    data.reserve(2);
 | 
			
		||||
    data.push_back(rct::sk2rct(key));
 | 
			
		||||
| 
						 | 
				
			
			@ -57,134 +62,79 @@ namespace cryptonote
 | 
			
		|||
    memwipe(&data[0], sizeof(rct::key));
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
 | 
			
		||||
  {
 | 
			
		||||
    // the multisig spend public key is the sum of all spend public keys
 | 
			
		||||
    multisig_keys.clear();
 | 
			
		||||
    const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key");
 | 
			
		||||
    for (const auto &k: spend_keys)
 | 
			
		||||
      rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k));
 | 
			
		||||
    multisig_keys.push_back(spend_secret_key);
 | 
			
		||||
    spend_skey = rct::sk2rct(spend_secret_key);
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey)
 | 
			
		||||
  {
 | 
			
		||||
    multisig_keys.clear();
 | 
			
		||||
    spend_pkey = rct::identity();
 | 
			
		||||
    spend_skey = rct::zero();
 | 
			
		||||
 | 
			
		||||
    // create all our composite private keys
 | 
			
		||||
    crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
 | 
			
		||||
    for (const auto &k: spend_keys)
 | 
			
		||||
    {
 | 
			
		||||
      rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
 | 
			
		||||
      crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk));
 | 
			
		||||
      memwipe(&sk, sizeof(sk));
 | 
			
		||||
      multisig_keys.push_back(msk);
 | 
			
		||||
      sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations)
 | 
			
		||||
  {
 | 
			
		||||
    std::vector<crypto::public_key> multisig_keys;
 | 
			
		||||
    crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key);
 | 
			
		||||
    for (const auto &k: derivations)
 | 
			
		||||
    {
 | 
			
		||||
      rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey));
 | 
			
		||||
      multisig_keys.push_back(rct::rct2pk(d));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return multisig_keys;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys)
 | 
			
		||||
  {
 | 
			
		||||
    rct::key secret_key = rct::zero();
 | 
			
		||||
    for (const auto &k: multisig_keys)
 | 
			
		||||
    {
 | 
			
		||||
      sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rct::rct2sk(secret_key);
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations)
 | 
			
		||||
  {
 | 
			
		||||
    std::vector<crypto::secret_key> multisig_keys;
 | 
			
		||||
    multisig_keys.reserve(derivations.size());
 | 
			
		||||
 | 
			
		||||
    for (const auto &k: derivations)
 | 
			
		||||
    {
 | 
			
		||||
      multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return multisig_keys;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys)
 | 
			
		||||
  {
 | 
			
		||||
    crypto::secret_key view_skey = get_multisig_blinded_secret_key(skey);
 | 
			
		||||
    for (const auto &k: skeys)
 | 
			
		||||
      sc_add((unsigned char*)&view_skey, rct::sk2rct(view_skey).bytes, rct::sk2rct(k).bytes);
 | 
			
		||||
    return view_skey;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys)
 | 
			
		||||
  {
 | 
			
		||||
    rct::key spend_public_key = rct::identity();
 | 
			
		||||
    for (const auto &pk: pkeys)
 | 
			
		||||
    {
 | 
			
		||||
      rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk));
 | 
			
		||||
    }
 | 
			
		||||
    return rct::rct2pk(spend_public_key);
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki)
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool generate_multisig_key_image(const cryptonote::account_keys &keys,
 | 
			
		||||
    std::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;
 | 
			
		||||
    crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R)
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void generate_multisig_LR(const crypto::public_key pkey,
 | 
			
		||||
    const crypto::secret_key &k,
 | 
			
		||||
    crypto::public_key &L,
 | 
			
		||||
    crypto::public_key &R)
 | 
			
		||||
  {
 | 
			
		||||
    rct::scalarmultBase((rct::key&)L, rct::sk2rct(k));
 | 
			
		||||
    crypto::generate_key_image(pkey, k, (crypto::key_image&)R);
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki)
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys,
 | 
			
		||||
    const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
 | 
			
		||||
    const crypto::public_key &out_key,
 | 
			
		||||
    const crypto::public_key &tx_public_key,
 | 
			
		||||
    const std::vector<crypto::public_key> &additional_tx_public_keys,
 | 
			
		||||
    std::size_t real_output_index,
 | 
			
		||||
    const std::vector<crypto::key_image> &pkis,
 | 
			
		||||
    crypto::key_image &ki)
 | 
			
		||||
  {
 | 
			
		||||
    // create a multisig partial key image
 | 
			
		||||
    // KI_partial = ([view key component] + [subaddress component] + [multisig privkeys]) * Hp(output one-time address)
 | 
			
		||||
    // - the 'multisig priv keys' here are those held by the local account
 | 
			
		||||
    // - later, we add in the components held by other participants
 | 
			
		||||
    cryptonote::keypair in_ephemeral;
 | 
			
		||||
    if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki, keys.get_device()))
 | 
			
		||||
      return false;
 | 
			
		||||
    std::unordered_set<crypto::key_image> used;
 | 
			
		||||
    for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m)
 | 
			
		||||
 | 
			
		||||
    // create a key image component for each of the local account's multisig private keys
 | 
			
		||||
    for (std::size_t m = 0; m < keys.m_multisig_keys.size(); ++m)
 | 
			
		||||
    {
 | 
			
		||||
      crypto::key_image pki;
 | 
			
		||||
      bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki);
 | 
			
		||||
      // pki = keys.m_multisig_keys[m] * Hp(out_key)
 | 
			
		||||
      // pki = key image component
 | 
			
		||||
      // out_key = one-time address of an output owned by the multisig group
 | 
			
		||||
      bool r = generate_multisig_key_image(keys, m, out_key, pki);
 | 
			
		||||
      if (!r)
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
      // this KI component is 'used' because it was included in the partial key image 'ki' above
 | 
			
		||||
      used.insert(pki);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // add the KI components from other participants to the partial KI
 | 
			
		||||
    // if they not included yet
 | 
			
		||||
    for (const auto &pki: pkis)
 | 
			
		||||
    {
 | 
			
		||||
      if (used.find(pki) == used.end())
 | 
			
		||||
      {
 | 
			
		||||
        // ignore components that have already been 'used'
 | 
			
		||||
        used.insert(pki);
 | 
			
		||||
 | 
			
		||||
        // KI_partial = KI_partial + KI_component[...]
 | 
			
		||||
        rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // at the end, 'ki' will hold the true key image for our output if inputs were sufficient
 | 
			
		||||
    // - if 'pkis' (the other participants' KI components) is missing some components
 | 
			
		||||
    //   then 'ki' will not be complete
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  //-----------------------------------------------------------------
 | 
			
		||||
  uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold");
 | 
			
		||||
    return participants - threshold + 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
// Copyright (c) 2017-2020, The Monero Project
 | 
			
		||||
// Copyright (c) 2017-2021, The Monero Project
 | 
			
		||||
// 
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
// 
 | 
			
		||||
| 
						 | 
				
			
			@ -28,44 +28,42 @@
 | 
			
		|||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "cryptonote_basic/cryptonote_format_utils.h"
 | 
			
		||||
#include "ringct/rctTypes.h"
 | 
			
		||||
 | 
			
		||||
namespace cryptonote
 | 
			
		||||
{
 | 
			
		||||
  struct account_keys;
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace cryptonote { struct account_keys; }
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  /**
 | 
			
		||||
  * @brief get_multisig_blinded_secret_key - converts an input private key into a blinded multisig private key
 | 
			
		||||
  *    Use 1a: converts account private spend key into multisig private key, which is used for key exchange and message signing
 | 
			
		||||
  *    Use 1b: converts account private view key into ancillary private key share, for the composite multisig private view key
 | 
			
		||||
  *    Use 2: converts DH shared secrets (curve points) into private keys, which are intermediate private keys in multisig key exchange
 | 
			
		||||
  * @param key - private key to transform
 | 
			
		||||
  * @return transformed private key
 | 
			
		||||
  */
 | 
			
		||||
  crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key);
 | 
			
		||||
  void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
 | 
			
		||||
  void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey);
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief generate_multisig_derivations performs common DH key derivation.
 | 
			
		||||
   *    Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant.
 | 
			
		||||
   *    this functions does the following: new multisig key = secret spend * public multisig key
 | 
			
		||||
   * @param keys - current wallet's keys
 | 
			
		||||
   * @param derivations - public multisig keys of other participants
 | 
			
		||||
   * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants
 | 
			
		||||
   */
 | 
			
		||||
  std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations);
 | 
			
		||||
  crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations);
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi)
 | 
			
		||||
   * @param derivations - others' participants public multisig keys.
 | 
			
		||||
   * @return vector of current wallet's multisig secret keys
 | 
			
		||||
   */
 | 
			
		||||
  std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations);
 | 
			
		||||
  crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys);
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys
 | 
			
		||||
   * @param pkeys unique public multisig keys
 | 
			
		||||
   * @return multisig wallet's spend public key
 | 
			
		||||
   */
 | 
			
		||||
  crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys);
 | 
			
		||||
  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 std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki);
 | 
			
		||||
  uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  bool generate_multisig_key_image(const cryptonote::account_keys &keys,
 | 
			
		||||
    std::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 cryptonote::account_keys &keys,
 | 
			
		||||
    const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
 | 
			
		||||
    const crypto::public_key &out_key,
 | 
			
		||||
    const crypto::public_key &tx_public_key,
 | 
			
		||||
    const std::vector<crypto::public_key> &additional_tx_public_keys,
 | 
			
		||||
    std::size_t real_output_index,
 | 
			
		||||
    const std::vector<crypto::key_image> &pkis,
 | 
			
		||||
    crypto::key_image &ki);
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										184
									
								
								src/multisig/multisig_account.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/multisig/multisig_account.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,184 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
//
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
//
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
//
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
//
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
//
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#include "multisig_account.h"
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "cryptonote_config.h"
 | 
			
		||||
#include "include_base_utils.h"
 | 
			
		||||
#include "multisig.h"
 | 
			
		||||
#include "multisig_kex_msg.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
#include "ringct/rctTypes.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#undef MONERO_DEFAULT_LOG_CATEGORY
 | 
			
		||||
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  multisig_account::multisig_account(const crypto::secret_key &base_privkey,
 | 
			
		||||
    const crypto::secret_key &base_common_privkey) :
 | 
			
		||||
      m_base_privkey{base_privkey},
 | 
			
		||||
      m_base_common_privkey{base_common_privkey},
 | 
			
		||||
      m_multisig_pubkey{rct::rct2pk(rct::identity())},
 | 
			
		||||
      m_common_pubkey{rct::rct2pk(rct::identity())},
 | 
			
		||||
      m_kex_rounds_complete{0},
 | 
			
		||||
      m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector<crypto::public_key>{}, base_common_privkey}.get_msg()}
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey),
 | 
			
		||||
      "Failed to derive public key");
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  multisig_account::multisig_account(const std::uint32_t threshold,
 | 
			
		||||
    std::vector<crypto::public_key> signers,
 | 
			
		||||
    const crypto::secret_key &base_privkey,
 | 
			
		||||
    const crypto::secret_key &base_common_privkey,
 | 
			
		||||
    std::vector<crypto::secret_key> multisig_privkeys,
 | 
			
		||||
    const crypto::secret_key &common_privkey,
 | 
			
		||||
    const crypto::public_key &multisig_pubkey,
 | 
			
		||||
    const crypto::public_key &common_pubkey,
 | 
			
		||||
    const std::uint32_t kex_rounds_complete,
 | 
			
		||||
    kex_origins_map_t kex_origins_map,
 | 
			
		||||
    std::string next_round_kex_message) :
 | 
			
		||||
      m_base_privkey{base_privkey},
 | 
			
		||||
      m_base_common_privkey{base_common_privkey},
 | 
			
		||||
      m_multisig_privkeys{std::move(multisig_privkeys)},
 | 
			
		||||
      m_common_privkey{common_privkey},
 | 
			
		||||
      m_multisig_pubkey{multisig_pubkey},
 | 
			
		||||
      m_common_pubkey{common_pubkey},
 | 
			
		||||
      m_kex_rounds_complete{kex_rounds_complete},
 | 
			
		||||
      m_kex_keys_to_origins_map{std::move(kex_origins_map)},
 | 
			
		||||
      m_next_round_kex_message{std::move(next_round_kex_message)}
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey),
 | 
			
		||||
      "Failed to derive public key");
 | 
			
		||||
    set_multisig_config(threshold, std::move(signers));
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool multisig_account::account_is_active() const
 | 
			
		||||
  {
 | 
			
		||||
    return m_kex_rounds_complete > 0;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool multisig_account::multisig_is_ready() const
 | 
			
		||||
  {
 | 
			
		||||
    if (account_is_active())
 | 
			
		||||
      return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete;
 | 
			
		||||
    else
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
    //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers)
 | 
			
		||||
  {
 | 
			
		||||
    // validate
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS,
 | 
			
		||||
      "multisig account: tried to set invalid number of signers.");
 | 
			
		||||
 | 
			
		||||
    for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it)
 | 
			
		||||
    {
 | 
			
		||||
      // signers should all be unique
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it,
 | 
			
		||||
        "multisig account: tried to set signers, but found a duplicate signer unexpectedly.");
 | 
			
		||||
 | 
			
		||||
      // signer pubkeys must be in main subgroup, and not identity
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())),
 | 
			
		||||
        "multisig account: tried to set signers, but a signer pubkey is invalid.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // own pubkey should be in signers list
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(),
 | 
			
		||||
      "multisig account: tried to set signers, but did not find the account's base pubkey in signer list.");
 | 
			
		||||
 | 
			
		||||
    // sort signers
 | 
			
		||||
    std::sort(signers.begin(), signers.end(),
 | 
			
		||||
      [](const crypto::public_key &key1, const crypto::public_key &key2) -> bool
 | 
			
		||||
        {
 | 
			
		||||
          return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    // set
 | 
			
		||||
    m_threshold = threshold;
 | 
			
		||||
    m_signers = std::move(signers);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::initialize_kex(const std::uint32_t threshold,
 | 
			
		||||
    std::vector<crypto::public_key> signers,
 | 
			
		||||
    const std::vector<multisig_kex_msg> &expanded_msgs_rnd1)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized");
 | 
			
		||||
 | 
			
		||||
    // only mutate account if update succeeds
 | 
			
		||||
    multisig_account temp_account{*this};
 | 
			
		||||
    temp_account.set_multisig_config(threshold, std::move(signers));
 | 
			
		||||
    temp_account.kex_update_impl(expanded_msgs_rnd1);
 | 
			
		||||
    *this = std::move(temp_account);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete.");
 | 
			
		||||
 | 
			
		||||
    multisig_account temp_account{*this};
 | 
			
		||||
    temp_account.kex_update_impl(expanded_msgs);
 | 
			
		||||
    *this = std::move(temp_account);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1");
 | 
			
		||||
    return num_signers - threshold + 1;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
							
								
								
									
										246
									
								
								src/multisig/multisig_account.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								src/multisig/multisig_account.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,246 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
// 
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
// 
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
// 
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
// 
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
// 
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
// 
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "multisig_kex_msg.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  /**
 | 
			
		||||
  * multisig account:
 | 
			
		||||
  * 
 | 
			
		||||
  * - handles account keys for an M-of-N multisig participant (M <= N; M >= 1; N >= 2)
 | 
			
		||||
  * - encapsulates multisig account construction process (via key exchange [kex])
 | 
			
		||||
  * - TODO: encapsulates key preparation for aggregation-style signing
 | 
			
		||||
  *
 | 
			
		||||
  * :: multisig pubkey: the private key is split, M group participants are required to reassemble (e.g. to sign something)
 | 
			
		||||
  *    - in cryptonote, this is the multisig spend key
 | 
			
		||||
  * :: multisig common pubkey: the private key is known to all participants (e.g. for authenticating as a group member)
 | 
			
		||||
  *    - in cryptonote, this is the multisig view key
 | 
			
		||||
  * 
 | 
			
		||||
  * 
 | 
			
		||||
  * multisig key exchange:
 | 
			
		||||
  * 
 | 
			
		||||
  * An 'M-of-N' (M <= N; M >= 1; N >= 2) multisignature key is a public key where at least 'M' out of 'N'
 | 
			
		||||
  * possible co-signers must collaborate in order to create a signature.
 | 
			
		||||
  * 
 | 
			
		||||
  * Constructing a multisig key involves a series of Diffie-Hellman exchanges between participants.
 | 
			
		||||
  * At the end of key exchange (kex), each participant will hold a number of private keys. Each private
 | 
			
		||||
  * key is shared by a group of (N - M + 1) participants. This way if (N - M) co-signers are missing, every
 | 
			
		||||
  * private key will be held by at least one of the remaining M people.
 | 
			
		||||
  * 
 | 
			
		||||
  * Note on MULTISIG_MAX_SIGNERS: During key exchange, participants will have up to '(N - 1) choose (N - M)'
 | 
			
		||||
  *   key shares. If N is large, then the max number of key shares (when M = (N-1)/2) can be huge. A limit of N <= 16 was
 | 
			
		||||
  *   arbitrarily chosen as a power of 2 that can accomodate the vast majority of practical use-cases. To increase the
 | 
			
		||||
  *   limit, FROST-style key aggregation should be used instead (it is more efficient than DH-based key generation
 | 
			
		||||
  *   when N - M > 1).
 | 
			
		||||
  * 
 | 
			
		||||
  * - Further reading
 | 
			
		||||
  *   - MRL-0009: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf
 | 
			
		||||
  *   - MuSig2: https://eprint.iacr.org/2020/1261
 | 
			
		||||
  *   - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3
 | 
			
		||||
  *   - FROST: https://eprint.iacr.org/2018/417
 | 
			
		||||
  */
 | 
			
		||||
  class multisig_account final
 | 
			
		||||
  {
 | 
			
		||||
  public:
 | 
			
		||||
  //member types
 | 
			
		||||
    using kex_origins_map_t = std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>;
 | 
			
		||||
 | 
			
		||||
  //constructors
 | 
			
		||||
    // default constructor
 | 
			
		||||
    multisig_account() = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * construct from base privkeys
 | 
			
		||||
    * 
 | 
			
		||||
    * - prepares a kex msg for the first round of multisig key construction.
 | 
			
		||||
    *    - the local account's kex msgs are signed with the base_privkey
 | 
			
		||||
    *    - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey
 | 
			
		||||
    */
 | 
			
		||||
    multisig_account(const crypto::secret_key &base_privkey,
 | 
			
		||||
      const crypto::secret_key &base_common_privkey);
 | 
			
		||||
 | 
			
		||||
    // reconstruct from full account details (not recommended)
 | 
			
		||||
    multisig_account(const std::uint32_t threshold,
 | 
			
		||||
      std::vector<crypto::public_key> signers,
 | 
			
		||||
      const crypto::secret_key &base_privkey,
 | 
			
		||||
      const crypto::secret_key &base_common_privkey,
 | 
			
		||||
      std::vector<crypto::secret_key> multisig_privkeys,
 | 
			
		||||
      const crypto::secret_key &common_privkey,
 | 
			
		||||
      const crypto::public_key &multisig_pubkey,
 | 
			
		||||
      const crypto::public_key &common_pubkey,
 | 
			
		||||
      const std::uint32_t kex_rounds_complete,
 | 
			
		||||
      kex_origins_map_t kex_origins_map,
 | 
			
		||||
      std::string next_round_kex_message);
 | 
			
		||||
 | 
			
		||||
    // copy constructor: default
 | 
			
		||||
 | 
			
		||||
  //destructor: default
 | 
			
		||||
    ~multisig_account() = default;
 | 
			
		||||
 | 
			
		||||
  //overloaded operators: none
 | 
			
		||||
 | 
			
		||||
  //getters
 | 
			
		||||
    // get threshold
 | 
			
		||||
    std::uint32_t get_threshold() const { return m_threshold; }
 | 
			
		||||
    // get signers
 | 
			
		||||
    const std::vector<crypto::public_key>& get_signers() const { return m_signers; }
 | 
			
		||||
    // get base privkey
 | 
			
		||||
    const crypto::secret_key& get_base_privkey() const { return m_base_privkey; }
 | 
			
		||||
    // get base pubkey
 | 
			
		||||
    const crypto::public_key& get_base_pubkey() const { return m_base_pubkey; }
 | 
			
		||||
    // get base common privkey
 | 
			
		||||
    const crypto::secret_key& get_base_common_privkey() const { return m_base_common_privkey; }
 | 
			
		||||
    // get multisig privkeys
 | 
			
		||||
    const std::vector<crypto::secret_key>& get_multisig_privkeys() const { return m_multisig_privkeys; }
 | 
			
		||||
    // get common privkey
 | 
			
		||||
    const crypto::secret_key& get_common_privkey() const { return m_common_privkey; }
 | 
			
		||||
    // get multisig pubkey
 | 
			
		||||
    const crypto::public_key& get_multisig_pubkey() const { return m_multisig_pubkey; }
 | 
			
		||||
    // get common pubkey
 | 
			
		||||
    const crypto::public_key& get_common_pubkey() const { return m_common_pubkey; }
 | 
			
		||||
    // get kex rounds complete
 | 
			
		||||
    std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; }
 | 
			
		||||
    // get kex keys to origins map
 | 
			
		||||
    const kex_origins_map_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; }
 | 
			
		||||
    // get the kex msg for the next round
 | 
			
		||||
    const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; }
 | 
			
		||||
 | 
			
		||||
  //account status functions
 | 
			
		||||
    // account has been intialized, and the account holder can use the 'common' key
 | 
			
		||||
    bool account_is_active() const;
 | 
			
		||||
    // account is ready to make multisig signatures
 | 
			
		||||
    bool multisig_is_ready() const;
 | 
			
		||||
 | 
			
		||||
  //account helpers
 | 
			
		||||
  private:
 | 
			
		||||
    // set the threshold (M) and signers (N)
 | 
			
		||||
    void set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers);
 | 
			
		||||
 | 
			
		||||
  //account mutators: key exchange to set up account
 | 
			
		||||
  public:
 | 
			
		||||
    /**
 | 
			
		||||
    * brief: initialize_kex - initialize key exchange
 | 
			
		||||
    *    - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds.
 | 
			
		||||
    */
 | 
			
		||||
    void initialize_kex(const std::uint32_t threshold,
 | 
			
		||||
      std::vector<crypto::public_key> signers,
 | 
			
		||||
      const std::vector<multisig_kex_msg> &expanded_msgs_rnd1);
 | 
			
		||||
    /**
 | 
			
		||||
    * brief: kex_update - Complete the 'in progress' kex round and set the kex message for the next round.
 | 
			
		||||
    *    - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds.
 | 
			
		||||
    *    - The main interface for multisig key exchange, this handles all the work of processing input messages,
 | 
			
		||||
    *      creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete.
 | 
			
		||||
    * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round
 | 
			
		||||
    */
 | 
			
		||||
    void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs);
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    // implementation of kex_update() (non-transactional)
 | 
			
		||||
    void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs);
 | 
			
		||||
    /**
 | 
			
		||||
    * brief: initialize_kex_update - Helper for kex_update_impl()
 | 
			
		||||
    *    - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key
 | 
			
		||||
    *      if appropriate.
 | 
			
		||||
    * param: expanded_msgs - set of multisig kex messages to process
 | 
			
		||||
    * param: rounds_required - number of rounds required for kex
 | 
			
		||||
    * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round'
 | 
			
		||||
    *    - If 'current_round' is the final round, these are the local account's shares of the final aggregate key.
 | 
			
		||||
    */
 | 
			
		||||
    void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
 | 
			
		||||
      const std::uint32_t rounds_required,
 | 
			
		||||
      std::vector<crypto::public_key> &exclude_pubkeys_out);
 | 
			
		||||
    /**
 | 
			
		||||
    * brief: finalize_kex_update - Helper for kex_update_impl()
 | 
			
		||||
    * param: rounds_required - number of rounds required for kex
 | 
			
		||||
    * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to
 | 
			
		||||
    * inoutparam: temp_account_inout - account to perform last update steps on
 | 
			
		||||
    */
 | 
			
		||||
    void finalize_kex_update(const std::uint32_t rounds_required,
 | 
			
		||||
      kex_origins_map_t result_keys_to_origins_map);
 | 
			
		||||
 | 
			
		||||
  //member variables
 | 
			
		||||
  private:
 | 
			
		||||
    /// misc. account details
 | 
			
		||||
    // [M] minimum number of co-signers to sign a message with the aggregate pubkey
 | 
			
		||||
    std::uint32_t m_threshold{0};
 | 
			
		||||
    // [N] base keys of all participants in the multisig (used to initiate key exchange, and as participant ids for msg signing)
 | 
			
		||||
    std::vector<crypto::public_key> m_signers;
 | 
			
		||||
 | 
			
		||||
    /// local participant's personal keys
 | 
			
		||||
    // base keypair of the participant
 | 
			
		||||
    // - used for signing messages, as the initial base key for key exchange, and to make DH derivations for key exchange
 | 
			
		||||
    crypto::secret_key m_base_privkey;
 | 
			
		||||
    crypto::public_key m_base_pubkey;
 | 
			
		||||
    // common base privkey, used to produce the aggregate common privkey
 | 
			
		||||
    crypto::secret_key m_base_common_privkey;
 | 
			
		||||
 | 
			
		||||
    /// core multisig account keys
 | 
			
		||||
    // the account's private key shares of the multisig address
 | 
			
		||||
    // TODO: also record which other signers have these privkeys, to enable aggregation signing (instead of round-robin)
 | 
			
		||||
    std::vector<crypto::secret_key> m_multisig_privkeys;
 | 
			
		||||
    // a privkey owned by all multisig participants (e.g. a cryptonote view key)
 | 
			
		||||
    crypto::secret_key m_common_privkey;
 | 
			
		||||
    // the multisig public key (e.g. a cryptonote spend key)
 | 
			
		||||
    crypto::public_key m_multisig_pubkey;
 | 
			
		||||
    // the common public key (e.g. a view spend key)
 | 
			
		||||
    crypto::public_key m_common_pubkey;
 | 
			
		||||
 | 
			
		||||
    /// kex variables
 | 
			
		||||
    // number of key exchange rounds that have been completed (all messages for the round collected and processed)
 | 
			
		||||
    std::uint32_t m_kex_rounds_complete{0};
 | 
			
		||||
    // this account's pubkeys for the in-progress key exchange round
 | 
			
		||||
    // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done)
 | 
			
		||||
    kex_origins_map_t m_kex_keys_to_origins_map;
 | 
			
		||||
    // the account's message for the in-progress key exchange round
 | 
			
		||||
    std::string m_next_round_kex_message;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * brief: multisig_kex_rounds_required - The number of key exchange rounds required to produce an M-of-N shared key.
 | 
			
		||||
  *    - Key exchange (kex) is a synchronous series of 'rounds'. In an 'active round', participants send messages
 | 
			
		||||
  *      to each other.
 | 
			
		||||
  *    - A participant considers a round 'complete' when they have collected sufficient messages
 | 
			
		||||
  *      from other participants, processed those messages, and updated their multisig account state.
 | 
			
		||||
  *    - Typically (as implemented in this module), completing a round coincides with making a message for the next round.
 | 
			
		||||
  * param: num_signers - number of participants in multisig (N)
 | 
			
		||||
  * param: threshold - threshold of multisig (M)
 | 
			
		||||
  * return: number of kex rounds required
 | 
			
		||||
  */
 | 
			
		||||
  std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold);
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
							
								
								
									
										726
									
								
								src/multisig/multisig_account_kex_impl.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										726
									
								
								src/multisig/multisig_account_kex_impl.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,726 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
//
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
//
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
//
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
//
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
//
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#include "multisig_account.h"
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "cryptonote_config.h"
 | 
			
		||||
#include "include_base_utils.h"
 | 
			
		||||
#include "multisig.h"
 | 
			
		||||
#include "multisig_kex_msg.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
 | 
			
		||||
#include <boost/math/special_functions/binomial.hpp>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <limits>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#undef MONERO_DEFAULT_LOG_CATEGORY
 | 
			
		||||
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  * 
 | 
			
		||||
  * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key
 | 
			
		||||
  *    Converts an input public key into a crypto private key (type cast, does not change serialization),
 | 
			
		||||
  *    then passes it to get_multisig_blinded_secret_key().
 | 
			
		||||
  * 
 | 
			
		||||
  *    Result:
 | 
			
		||||
  *      - privkey = H(derivation)
 | 
			
		||||
  *      - pubkey = privkey * G
 | 
			
		||||
  * param: derivation - a curve point
 | 
			
		||||
  * outparam: derived_pubkey_out - public key of the resulting privkey
 | 
			
		||||
  * return: multisig private key
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static crypto::secret_key calculate_multisig_keypair_from_derivation(const crypto::public_key_memsafe &derivation,
 | 
			
		||||
    crypto::public_key &derived_pubkey_out)
 | 
			
		||||
  {
 | 
			
		||||
    crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(derivation)));
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(blinded_skey, derived_pubkey_out), "Failed to derive public key");
 | 
			
		||||
 | 
			
		||||
    return blinded_skey;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  *
 | 
			
		||||
  * brief: make_multisig_common_privkey - Create the 'common' multisig privkey, owned by all multisig participants.
 | 
			
		||||
  *    - common privkey = H(sorted base common privkeys)
 | 
			
		||||
  * param: participant_base_common_privkeys - Base common privkeys contributed by multisig participants.
 | 
			
		||||
  * outparam: common_privkey_out - result
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static void make_multisig_common_privkey(std::vector<crypto::secret_key> participant_base_common_privkeys,
 | 
			
		||||
    crypto::secret_key &common_privkey_out)
 | 
			
		||||
  {
 | 
			
		||||
    // sort the privkeys for consistency
 | 
			
		||||
    //TODO: need a constant-time operator< for sorting secret keys
 | 
			
		||||
    std::sort(participant_base_common_privkeys.begin(), participant_base_common_privkeys.end(),
 | 
			
		||||
        [](const crypto::secret_key &key1, const crypto::secret_key &key2) -> bool
 | 
			
		||||
        {
 | 
			
		||||
          return memcmp(&key1, &key2, sizeof(crypto::secret_key)) < 0;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    // privkey = H(sorted ancillary base privkeys)
 | 
			
		||||
    crypto::hash_to_scalar(participant_base_common_privkeys.data(),
 | 
			
		||||
      participant_base_common_privkeys.size()*sizeof(crypto::secret_key),
 | 
			
		||||
      common_privkey_out);
 | 
			
		||||
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(common_privkey_out != crypto::null_skey, "Unexpected null secret key (danger!).");
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  * 
 | 
			
		||||
  * brief: compute_multisig_aggregation_coefficient - creates aggregation coefficient for a specific public key in a set
 | 
			
		||||
  *    of public keys
 | 
			
		||||
  *    
 | 
			
		||||
  *    WARNING: The coefficient will only be deterministic if...
 | 
			
		||||
  *      1) input keys are pre-sorted
 | 
			
		||||
  *         - tested here
 | 
			
		||||
  *      2) input keys are in canonical form (compressed points in the prime-order subgroup of Ed25519)
 | 
			
		||||
  *         - untested here for performance
 | 
			
		||||
  * param: sorted_keys - set of component public keys that will be merged into a multisig public spend key
 | 
			
		||||
  * param: aggregation_key - one of the component public keys
 | 
			
		||||
  * return: aggregation coefficient
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static rct::key compute_multisig_aggregation_coefficient(const std::vector<crypto::public_key> &sorted_keys,
 | 
			
		||||
    const crypto::public_key &aggregation_key)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(std::is_sorted(sorted_keys.begin(), sorted_keys.end()),
 | 
			
		||||
      "Keys for aggregation coefficient aren't sorted.");
 | 
			
		||||
 | 
			
		||||
    // aggregation key must be in sorted_keys
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(std::find(sorted_keys.begin(), sorted_keys.end(), aggregation_key) != sorted_keys.end(),
 | 
			
		||||
      "Aggregation key expected to be in input keyset.");
 | 
			
		||||
 | 
			
		||||
    // aggregation coefficient salt
 | 
			
		||||
    rct::key salt = rct::zero();
 | 
			
		||||
    static_assert(sizeof(rct::key) >= sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION), "Hash domain separator is too big.");
 | 
			
		||||
    memcpy(salt.bytes, config::HASH_KEY_MULTISIG_KEY_AGGREGATION, sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION));
 | 
			
		||||
 | 
			
		||||
    // coeff = H(aggregation_key, sorted_keys, domain-sep)
 | 
			
		||||
    rct::keyV data;
 | 
			
		||||
    data.reserve(sorted_keys.size() + 2);
 | 
			
		||||
    data.push_back(rct::pk2rct(aggregation_key));
 | 
			
		||||
    for (const auto &key : sorted_keys)
 | 
			
		||||
      data.push_back(rct::pk2rct(key));
 | 
			
		||||
    data.push_back(salt);
 | 
			
		||||
 | 
			
		||||
    // note: coefficient is considered public knowledge, no need to memwipe data
 | 
			
		||||
    return rct::hash_to_scalar(data);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  * 
 | 
			
		||||
  * brief: generate_multisig_aggregate_key - generates a multisig public spend key via key aggregation
 | 
			
		||||
  *    Key aggregation via aggregation coefficients prevents key cancellation attacks.
 | 
			
		||||
  *    See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf
 | 
			
		||||
  * param: final_keys - address components (public keys) obtained from other participants (not shared with local)
 | 
			
		||||
  * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference)
 | 
			
		||||
  * return: final multisig public spend key for the account
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static crypto::public_key generate_multisig_aggregate_key(std::vector<crypto::public_key> final_keys,
 | 
			
		||||
    std::vector<crypto::secret_key> &privkeys_inout)
 | 
			
		||||
  {
 | 
			
		||||
    // collect all public keys that will go into the spend key (these don't need to be memsafe)
 | 
			
		||||
    final_keys.reserve(final_keys.size() + privkeys_inout.size());
 | 
			
		||||
 | 
			
		||||
    // 1. convert local multisig private keys to pub keys
 | 
			
		||||
    // 2. insert to final keyset if not there yet
 | 
			
		||||
    // 3. save the corresponding index of input priv key set for later reference
 | 
			
		||||
    std::unordered_map<crypto::public_key, std::size_t> own_keys_mapping;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index)
 | 
			
		||||
    {
 | 
			
		||||
      crypto::public_key pubkey;
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key");
 | 
			
		||||
 | 
			
		||||
      own_keys_mapping[pubkey] = multisig_keys_index;
 | 
			
		||||
 | 
			
		||||
      final_keys.push_back(pubkey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // sort input final keys for computing aggregation coefficients (lowest to highest)
 | 
			
		||||
    // note: input should be sanitized (no duplicates)
 | 
			
		||||
    std::sort(final_keys.begin(), final_keys.end());
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(final_keys.begin(), final_keys.end()) == final_keys.end(),
 | 
			
		||||
        "Unexpected duplicate found in input list.");
 | 
			
		||||
 | 
			
		||||
    // key aggregation
 | 
			
		||||
    rct::key aggregate_key = rct::identity();
 | 
			
		||||
 | 
			
		||||
    for (const crypto::public_key &key : final_keys)
 | 
			
		||||
    {
 | 
			
		||||
      // get aggregation coefficient
 | 
			
		||||
      rct::key coeff = compute_multisig_aggregation_coefficient(final_keys, key);
 | 
			
		||||
 | 
			
		||||
      // convert private key if possible
 | 
			
		||||
      // note: retain original priv key index in input list, in case order matters upstream
 | 
			
		||||
      auto found_key = own_keys_mapping.find(key);
 | 
			
		||||
      if (found_key != own_keys_mapping.end())
 | 
			
		||||
      {
 | 
			
		||||
        // k_agg = coeff*k_base
 | 
			
		||||
        sc_mul((unsigned char*)&(privkeys_inout[found_key->second]),
 | 
			
		||||
          coeff.bytes,
 | 
			
		||||
          (const unsigned char*)&(privkeys_inout[found_key->second]));
 | 
			
		||||
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES(privkeys_inout[found_key->second] != crypto::null_skey,
 | 
			
		||||
          "Multisig privkey with aggregation coefficient unexpectedly null.");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // convert public key (pre-merge operation)
 | 
			
		||||
      // K_agg = coeff*K_base
 | 
			
		||||
      rct::key converted_pubkey = rct::scalarmultKey(rct::pk2rct(key), coeff);
 | 
			
		||||
 | 
			
		||||
      // build aggregate key (merge operation)
 | 
			
		||||
      rct::addKeys(aggregate_key, aggregate_key, converted_pubkey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rct::rct2pk(aggregate_key);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  *
 | 
			
		||||
  * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction.
 | 
			
		||||
  *    - Involves DH exchanges with pubkeys provided by other participants.
 | 
			
		||||
  *    - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you].
 | 
			
		||||
  * param: base_privkey - account's base private key, for performing DH exchanges and signing messages
 | 
			
		||||
  * param: round - the round of the message that should be produced
 | 
			
		||||
  * param: threshold - threshold for multisig (M in M-of-N)
 | 
			
		||||
  * param: num_signers - number of participants in multisig (N)
 | 
			
		||||
  * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of
 | 
			
		||||
  *    participants who will share each derivation with you
 | 
			
		||||
  * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys
 | 
			
		||||
  *        - If msg is not for the last round, then these derivations are also stored in the output message
 | 
			
		||||
  *          so they can be sent to other participants, who will make more DH derivations for the next kex round.
 | 
			
		||||
  *        - If msg is for the last round, then these derivations won't be sent to other participants.
 | 
			
		||||
  *          Instead, they are converted to share secrets (i.e. s = H(derivation)) and multiplied by G.
 | 
			
		||||
  *          The keys s*G are sent to other participants in the message, so they can be used to produce the final
 | 
			
		||||
  *          multisig key via generate_multisig_spend_public_key().
 | 
			
		||||
  *            - The values s are the local account's shares of the final multisig key's private key. The caller can
 | 
			
		||||
  *              compute those values with calculate_multisig_keypair_from_derivation() (or compute them directly).
 | 
			
		||||
  * return: multisig kex message for the specified round
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static multisig_kex_msg multisig_kex_make_next_msg(const crypto::secret_key &base_privkey,
 | 
			
		||||
    const std::uint32_t round,
 | 
			
		||||
    const std::uint32_t threshold,
 | 
			
		||||
    const std::uint32_t num_signers,
 | 
			
		||||
    const std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &pubkey_origins_map,
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &derivation_origins_map_out)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS,
 | 
			
		||||
      "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange).");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold,
 | 
			
		||||
      "Multisig threshold may not be larger than number of signers.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(round > 1, "Round for next msg must be > 1.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold),
 | 
			
		||||
      "Trying to make key exchange message for an invalid round.");
 | 
			
		||||
 | 
			
		||||
    // make shared secrets with input pubkeys
 | 
			
		||||
    std::vector<crypto::public_key> msg_pubkeys;
 | 
			
		||||
    msg_pubkeys.reserve(pubkey_origins_map.size());
 | 
			
		||||
    derivation_origins_map_out.clear();
 | 
			
		||||
 | 
			
		||||
    for (const auto &pubkey_and_origins : pubkey_origins_map)
 | 
			
		||||
    {
 | 
			
		||||
      // D = 8 * k_base * K_pubkey
 | 
			
		||||
      // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local
 | 
			
		||||
      //       base_privkey is a multiple of 8 or not
 | 
			
		||||
      // note2: avoid making temporaries that won't be memwiped
 | 
			
		||||
      rct::key derivation_rct;
 | 
			
		||||
      auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{
 | 
			
		||||
        memwipe(&derivation_rct, sizeof(rct::key));
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey));
 | 
			
		||||
      rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT);
 | 
			
		||||
 | 
			
		||||
      crypto::public_key_memsafe derivation{rct::rct2pk(derivation_rct)};
 | 
			
		||||
 | 
			
		||||
      // retain mapping between pubkey's origins and the DH derivation
 | 
			
		||||
      // note: if msg for last round, then caller must know how to handle these derivations properly
 | 
			
		||||
      derivation_origins_map_out[derivation] = pubkey_and_origins.second;
 | 
			
		||||
 | 
			
		||||
      // if the last round, convert derivations to public keys for the output message
 | 
			
		||||
      if (round == multisig_kex_rounds_required(num_signers, threshold))
 | 
			
		||||
      {
 | 
			
		||||
        // derived_pubkey = H(derivation)*G
 | 
			
		||||
        crypto::public_key derived_pubkey;
 | 
			
		||||
        calculate_multisig_keypair_from_derivation(derivation, derived_pubkey);
 | 
			
		||||
        msg_pubkeys.push_back(derived_pubkey);
 | 
			
		||||
      }
 | 
			
		||||
      // otherwise, put derivations in message directly, so other signers can in turn create derivations (shared secrets)
 | 
			
		||||
      //  with them for the next round
 | 
			
		||||
      else
 | 
			
		||||
        msg_pubkeys.push_back(derivation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)};
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  *
 | 
			
		||||
  * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages.
 | 
			
		||||
  *    - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key,
 | 
			
		||||
  *      ignores messages signed by the local account, ignores keys found in input 'exclusion set',
 | 
			
		||||
  *      constructs map of pubkey:origins.
 | 
			
		||||
  *    - Requires that all input msgs have the same round number.
 | 
			
		||||
  *
 | 
			
		||||
  *    origins = all the signing pubkeys that recommended a given pubkey found in input msgs
 | 
			
		||||
  *
 | 
			
		||||
  *    - If the messages' round numbers are all '1', then only the message signing pubkey is considered
 | 
			
		||||
  *      'recommended'. Furthermore, the 'exclusion set' is ignored.
 | 
			
		||||
  * param: own_pubkey - local account's signing key (key used to sign multisig messages)
 | 
			
		||||
  * param: expanded_msgs - set of multisig kex messages to process
 | 
			
		||||
  * param: exclude_pubkeys - pubkeys to exclude from output set
 | 
			
		||||
  * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins
 | 
			
		||||
  * return: round number shared by all input msgs
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey,
 | 
			
		||||
    const std::vector<multisig_kex_msg> &expanded_msgs,
 | 
			
		||||
    const std::vector<crypto::public_key> &exclude_pubkeys,
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &sanitized_pubkeys_out)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected.");
 | 
			
		||||
 | 
			
		||||
    std::uint32_t round = expanded_msgs[0].get_round();
 | 
			
		||||
    sanitized_pubkeys_out.clear();
 | 
			
		||||
 | 
			
		||||
    // get all pubkeys from input messages, add them to pubkey:origins map
 | 
			
		||||
    // - origins = all the signing pubkeys that recommended a given msg pubkey
 | 
			
		||||
    for (const auto &expanded_msg : expanded_msgs)
 | 
			
		||||
    {
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number.");
 | 
			
		||||
 | 
			
		||||
      // ignore messages from self
 | 
			
		||||
      if (expanded_msg.get_signing_pubkey() == own_pubkey)
 | 
			
		||||
        continue;
 | 
			
		||||
 | 
			
		||||
      // in round 1, only the signing pubkey is treated as a msg pubkey
 | 
			
		||||
      if (round == 1)
 | 
			
		||||
      {
 | 
			
		||||
        // note: ignores duplicates
 | 
			
		||||
        sanitized_pubkeys_out[expanded_msg.get_signing_pubkey()].insert(expanded_msg.get_signing_pubkey());
 | 
			
		||||
      }
 | 
			
		||||
      // in other rounds, only the msg pubkeys are treated as msg pubkeys
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        // copy all pubkeys from message into list
 | 
			
		||||
        for (const auto &pubkey : expanded_msg.get_msg_pubkeys())
 | 
			
		||||
        {
 | 
			
		||||
          // ignore own pubkey
 | 
			
		||||
          if (pubkey == own_pubkey)
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
          // ignore pubkeys in 'ignore' set
 | 
			
		||||
          if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end())
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
          // note: ignores duplicates
 | 
			
		||||
          sanitized_pubkeys_out[pubkey].insert(expanded_msg.get_signing_pubkey());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return round;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  *
 | 
			
		||||
  * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round.
 | 
			
		||||
  *    - Sanitizes input msgs.
 | 
			
		||||
  *    - Require uniqueness in: 'signers', 'exclude_pubkeys'.
 | 
			
		||||
  *    - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers.
 | 
			
		||||
  *      - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be
 | 
			
		||||
  *        shared by (N - M + 1) signers.
 | 
			
		||||
  *    - Requires that msgs are signed by only keys in 'signers'.
 | 
			
		||||
  *    - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys.
 | 
			
		||||
  *      - These should be derivations each signer recommends for round 'expected_round', excluding derivations shared
 | 
			
		||||
  *        with the local account.
 | 
			
		||||
  *    - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys.
 | 
			
		||||
  *      - These should be derivations the local account has corresponding to round 'expected_round'.
 | 
			
		||||
  * param: base_privkey - multisig account's base private key
 | 
			
		||||
  * param: expected_round - expected kex round of input messages
 | 
			
		||||
  * param: threshold - threshold for multisig (M in M-of-N)
 | 
			
		||||
  * param: signers - expected participants in multisig kex
 | 
			
		||||
  * param: expanded_msgs - set of multisig kex messages to process
 | 
			
		||||
  * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round'
 | 
			
		||||
  * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluate_multisig_kex_round_msgs(
 | 
			
		||||
    const crypto::public_key &base_pubkey,
 | 
			
		||||
    const std::uint32_t expected_round,
 | 
			
		||||
    const std::uint32_t threshold,
 | 
			
		||||
    const std::vector<crypto::public_key> &signers,
 | 
			
		||||
    const std::vector<multisig_kex_msg> &expanded_msgs,
 | 
			
		||||
    const std::vector<crypto::public_key> &exclude_pubkeys)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(signers.size() > 1, "Must be at least one other multisig signer.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(signers.size() <= config::MULTISIG_MAX_SIGNERS,
 | 
			
		||||
      "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange).");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(signers.size() >= threshold, "Multisig threshold may not be larger than number of signers.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expected_round > 0, "Expected round must be > 0.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expected_round <= multisig_kex_rounds_required(signers.size(), threshold),
 | 
			
		||||
      "Expecting key exchange messages for an invalid round.");
 | 
			
		||||
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> pubkey_origins_map;
 | 
			
		||||
 | 
			
		||||
    // leave early in the last round of 1-of-N, where all signers share a key so the local signer doesn't care about
 | 
			
		||||
    // recommendations from other signers
 | 
			
		||||
    if (threshold == 1 && expected_round == multisig_kex_rounds_required(signers.size(), threshold))
 | 
			
		||||
      return pubkey_origins_map;
 | 
			
		||||
 | 
			
		||||
    // exclude_pubkeys should all be unique
 | 
			
		||||
    for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it)
 | 
			
		||||
    {
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(std::find(exclude_pubkeys.begin(), it, *it) == it,
 | 
			
		||||
        "Found duplicate pubkeys for exclusion unexpectedly.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // sanitize input messages
 | 
			
		||||
    std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map);
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(round == expected_round,
 | 
			
		||||
      "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]");
 | 
			
		||||
 | 
			
		||||
    // evaluate pubkeys collected
 | 
			
		||||
    std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map;
 | 
			
		||||
 | 
			
		||||
    // 1. each pubkey should be recommended by a precise number of signers
 | 
			
		||||
    for (const auto &pubkey_and_origins : pubkey_origins_map)
 | 
			
		||||
    {
 | 
			
		||||
      // expected amount = round_num
 | 
			
		||||
      // With each successive round, pubkeys are shared by incrementally larger groups,
 | 
			
		||||
      //  starting at 1 in round 1 (i.e. the local multisig key to start kex with).
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round,
 | 
			
		||||
        "A pubkey recommended by multisig kex messages had an unexpected number of recommendations.");
 | 
			
		||||
 | 
			
		||||
      // map (sanitized) pubkeys back to origins
 | 
			
		||||
      for (const auto &origin : pubkey_and_origins.second)
 | 
			
		||||
        origin_pubkeys_map[origin].insert(pubkey_and_origins.first);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer)
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1,
 | 
			
		||||
      "Number of unique other signers does not equal number of other signers that recommended pubkeys.");
 | 
			
		||||
 | 
			
		||||
    // 3. each origin should recommend a precise number of pubkeys
 | 
			
		||||
 | 
			
		||||
    // TODO: move to a 'math' library, with unit tests
 | 
			
		||||
    auto n_choose_k_f =
 | 
			
		||||
      [](const std::uint32_t n, const std::uint32_t k) -> std::uint32_t
 | 
			
		||||
      {
 | 
			
		||||
        static_assert(std::numeric_limits<std::int32_t>::digits <= std::numeric_limits<double>::digits,
 | 
			
		||||
          "n_choose_k requires no rounding issues when converting between int32 <-> double.");
 | 
			
		||||
 | 
			
		||||
        if (n < k)
 | 
			
		||||
          return 0;
 | 
			
		||||
 | 
			
		||||
        double fp_result = boost::math::binomial_coefficient<double>(n, k);
 | 
			
		||||
 | 
			
		||||
        if (fp_result < 0)
 | 
			
		||||
          return 0;
 | 
			
		||||
 | 
			
		||||
        if (fp_result > std::numeric_limits<std::int32_t>::max())  // note: std::round() returns std::int32_t
 | 
			
		||||
          return 0;
 | 
			
		||||
 | 
			
		||||
        return static_cast<std::uint32_t>(std::round(fp_result));
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    // other signers: (N - 2) choose (msg_round_num - 1)
 | 
			
		||||
      // - Each signer recommends keys they share with other signers.
 | 
			
		||||
      // - In each round, a signer shares a key with 'round num - 1' other signers.
 | 
			
		||||
      // - Since 'origins pubkey map' excludes keys shared with the local account,
 | 
			
		||||
      //   only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers).
 | 
			
		||||
      // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local).
 | 
			
		||||
      // - Each origin should have a shared key with each group of size 'round - 1'.
 | 
			
		||||
      // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may
 | 
			
		||||
      //       have boosted the local signer (implying they didn't have access to the local signer's previous round msg).
 | 
			
		||||
    std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1);
 | 
			
		||||
 | 
			
		||||
    // local: (N - 1) choose (msg_round_num - 1)
 | 
			
		||||
    std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1);
 | 
			
		||||
 | 
			
		||||
    // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0,
 | 
			
		||||
      "Bad num signers or round num (possibly numerical limits exceeded).");
 | 
			
		||||
 | 
			
		||||
    // check that local account recommends expected number of keys
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(exclude_pubkeys.size() == expected_recommendations_self,
 | 
			
		||||
      "Local account did not recommend expected number of multisig keys.");
 | 
			
		||||
 | 
			
		||||
    // check that other signers recommend expected number of keys
 | 
			
		||||
    for (const auto &origin_and_pubkeys : origin_pubkeys_map)
 | 
			
		||||
    {
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others,
 | 
			
		||||
        "A pubkey recommended by multisig kex messages had an unexpected number of recommendations.");
 | 
			
		||||
 | 
			
		||||
      // 2 (continued). only expected signers should be recommending keys
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(),
 | 
			
		||||
        "Multisig kex message with unexpected signer encountered.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // note: above tests implicitly detect if the total number of recommended keys is correct or not
 | 
			
		||||
    return pubkey_origins_map;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  /**
 | 
			
		||||
  * INTERNAL
 | 
			
		||||
  *
 | 
			
		||||
  * brief: multisig_kex_process_round - Process kex messages for the active kex round.
 | 
			
		||||
  *    - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg().
 | 
			
		||||
  *      - In other words, evaluate the input messages and try to make a message for the next round.
 | 
			
		||||
  *    - Note: Must be called on the final round's msgs to evaluate the final key components
 | 
			
		||||
  *            recommended by other participants.
 | 
			
		||||
  * param: base_privkey - multisig account's base private key
 | 
			
		||||
  * param: current_round - round of kex the input messages should be designed for
 | 
			
		||||
  * param: threshold - threshold for multisig (M in M-of-N)
 | 
			
		||||
  * param: signers - expected participants in multisig kex
 | 
			
		||||
  * param: expanded_msgs - set of multisig kex messages to process
 | 
			
		||||
  * param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round'
 | 
			
		||||
  *    - If 'current_round' is the final round, these are the local account's shares of the final aggregate key.
 | 
			
		||||
  * outparam: keys_to_origins_map_out - map between round keys and identity keys
 | 
			
		||||
  *    - If in the final round, these are key shares recommended by other signers for the final aggregate key.
 | 
			
		||||
  *    - Otherwise, these are the local account's DH derivations for the next round.
 | 
			
		||||
  *      - See multisig_kex_make_next_msg() for an explanation.
 | 
			
		||||
  * return: multisig kex message for next round, or empty message if 'current_round' is the final round
 | 
			
		||||
  */
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  static multisig_kex_msg multisig_kex_process_round(const crypto::secret_key &base_privkey,
 | 
			
		||||
    const crypto::public_key &base_pubkey,
 | 
			
		||||
    const std::uint32_t current_round,
 | 
			
		||||
    const std::uint32_t threshold,
 | 
			
		||||
    const std::vector<crypto::public_key> &signers,
 | 
			
		||||
    const std::vector<multisig_kex_msg> &expanded_msgs,
 | 
			
		||||
    const std::vector<crypto::public_key> &exclude_pubkeys,
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &keys_to_origins_map_out)
 | 
			
		||||
  {
 | 
			
		||||
    // evaluate messages
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluated_pubkeys =
 | 
			
		||||
      evaluate_multisig_kex_round_msgs(base_pubkey, current_round, threshold, signers, expanded_msgs, exclude_pubkeys);
 | 
			
		||||
 | 
			
		||||
    // produce message for next round (if there is one)
 | 
			
		||||
    if (current_round < multisig_kex_rounds_required(signers.size(), threshold))
 | 
			
		||||
    {
 | 
			
		||||
      return multisig_kex_make_next_msg(base_privkey,
 | 
			
		||||
        current_round + 1,
 | 
			
		||||
        threshold,
 | 
			
		||||
        signers.size(),
 | 
			
		||||
        evaluated_pubkeys,
 | 
			
		||||
        keys_to_origins_map_out);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // no more rounds, so collect the key shares recommended by other signers for the final aggregate key
 | 
			
		||||
      keys_to_origins_map_out.clear();
 | 
			
		||||
      keys_to_origins_map_out = std::move(evaluated_pubkeys);
 | 
			
		||||
 | 
			
		||||
      return multisig_kex_msg{};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs,
 | 
			
		||||
    const std::uint32_t rounds_required,
 | 
			
		||||
    std::vector<crypto::public_key> &exclude_pubkeys_out)
 | 
			
		||||
  {
 | 
			
		||||
    if (m_kex_rounds_complete == 0)
 | 
			
		||||
    {
 | 
			
		||||
      // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys
 | 
			
		||||
 | 
			
		||||
      // collect participants' base common privkey shares
 | 
			
		||||
      // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers
 | 
			
		||||
      //       will be blocked by duplicate-signer errors after this function is called
 | 
			
		||||
      std::vector<crypto::secret_key> participant_base_common_privkeys;
 | 
			
		||||
      participant_base_common_privkeys.reserve(expanded_msgs.size() + 1);
 | 
			
		||||
 | 
			
		||||
      // add local ancillary base privkey
 | 
			
		||||
      participant_base_common_privkeys.emplace_back(m_base_common_privkey);
 | 
			
		||||
 | 
			
		||||
      // add other signers' base common privkeys
 | 
			
		||||
      for (const auto &expanded_msg : expanded_msgs)
 | 
			
		||||
      {
 | 
			
		||||
        if (expanded_msg.get_signing_pubkey() != m_base_pubkey)
 | 
			
		||||
        {
 | 
			
		||||
          participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // make common privkey
 | 
			
		||||
      make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey);
 | 
			
		||||
 | 
			
		||||
      // set common pubkey
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey),
 | 
			
		||||
        "Failed to derive public key");
 | 
			
		||||
 | 
			
		||||
      // if N-of-N, then the base privkey will be used directly to make the account's share of the final key
 | 
			
		||||
      if (rounds_required == 1)
 | 
			
		||||
      {
 | 
			
		||||
        m_multisig_privkeys.clear();
 | 
			
		||||
        m_multisig_privkeys.emplace_back(m_base_privkey);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // exclude all keys the local account recommends
 | 
			
		||||
      // - in the first round, only the local pubkey is recommended by the local signer
 | 
			
		||||
      exclude_pubkeys_out.emplace_back(m_base_pubkey);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // in other rounds, kex msgs will contain participants' shared keys
 | 
			
		||||
 | 
			
		||||
      // ignore shared keys the account helped create for this round
 | 
			
		||||
      for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map)
 | 
			
		||||
      {
 | 
			
		||||
        exclude_pubkeys_out.emplace_back(shared_key_with_origins.first);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::finalize_kex_update(const std::uint32_t rounds_required,
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map)
 | 
			
		||||
  {
 | 
			
		||||
    // prepare for next round (or complete the multisig account fully)
 | 
			
		||||
    if (rounds_required == m_kex_rounds_complete + 1)
 | 
			
		||||
    {
 | 
			
		||||
      // finished (have set of msgs to complete address)
 | 
			
		||||
 | 
			
		||||
      // when 'completing the final round', result keys are other signers' shares of the final key
 | 
			
		||||
      std::vector<crypto::public_key> result_keys;
 | 
			
		||||
      result_keys.reserve(result_keys_to_origins_map.size());
 | 
			
		||||
 | 
			
		||||
      for (const auto &result_key_and_origins : result_keys_to_origins_map)
 | 
			
		||||
      {
 | 
			
		||||
        result_keys.emplace_back(result_key_and_origins.first);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied
 | 
			
		||||
      m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys);
 | 
			
		||||
 | 
			
		||||
      // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys)
 | 
			
		||||
      // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing
 | 
			
		||||
      m_kex_keys_to_origins_map.clear();
 | 
			
		||||
    }
 | 
			
		||||
    else if (rounds_required == m_kex_rounds_complete + 2)
 | 
			
		||||
    {
 | 
			
		||||
      // one more round (must send/receive one more set of kex msgs)
 | 
			
		||||
      // - at this point, have local signer's pre-aggregation private key shares of the final address
 | 
			
		||||
 | 
			
		||||
      // result keys are the local signer's DH derivations for the next round
 | 
			
		||||
 | 
			
		||||
      // derivations are shared secrets between each group of N - M + 1 signers of which the local account is a member
 | 
			
		||||
      // - convert them to private keys: multisig_key = H(derivation)
 | 
			
		||||
      // - note: shared key = multisig_key[i]*G is recorded in the kex msg for sending to other participants
 | 
			
		||||
      //   instead of the original 'derivation' value (which MUST be kept secret!)
 | 
			
		||||
      m_multisig_privkeys.clear();
 | 
			
		||||
      m_multisig_privkeys.reserve(result_keys_to_origins_map.size());
 | 
			
		||||
 | 
			
		||||
      m_kex_keys_to_origins_map.clear();
 | 
			
		||||
 | 
			
		||||
      for (const auto &derivation_and_origins : result_keys_to_origins_map)
 | 
			
		||||
      {
 | 
			
		||||
        // multisig_privkey = H(derivation)
 | 
			
		||||
        // derived pubkey = multisig_key * G
 | 
			
		||||
        crypto::public_key_memsafe derived_pubkey;
 | 
			
		||||
        m_multisig_privkeys.push_back(
 | 
			
		||||
          calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey));
 | 
			
		||||
 | 
			
		||||
        // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key]
 | 
			
		||||
        m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // next round is an 'intermediate' key exchange round, so there is nothing special to do here
 | 
			
		||||
 | 
			
		||||
      // save the account's kex keys for this round [DH derivation : other signers who will have the same derivation]
 | 
			
		||||
      m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // a full set of msgs has been collected and processed, so the 'round is complete'
 | 
			
		||||
    ++m_kex_rounds_complete;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_account: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs)
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "No key exchange messages passed in.");
 | 
			
		||||
 | 
			
		||||
    const std::uint32_t rounds_required = multisig_kex_rounds_required(m_signers.size(), m_threshold);
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(rounds_required > 0, "Multisig kex rounds required unexpectedly 0.");
 | 
			
		||||
 | 
			
		||||
    // initialize account update
 | 
			
		||||
    std::vector<crypto::public_key> exclude_pubkeys;
 | 
			
		||||
    initialize_kex_update(expanded_msgs, rounds_required, exclude_pubkeys);
 | 
			
		||||
 | 
			
		||||
    // evaluate messages and get this account's kex msg for the next round
 | 
			
		||||
    std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map;
 | 
			
		||||
 | 
			
		||||
    m_next_round_kex_message = multisig_kex_process_round(
 | 
			
		||||
      m_base_privkey,
 | 
			
		||||
      m_base_pubkey,
 | 
			
		||||
      m_kex_rounds_complete + 1,
 | 
			
		||||
      m_threshold,
 | 
			
		||||
      m_signers,
 | 
			
		||||
      expanded_msgs,
 | 
			
		||||
      exclude_pubkeys,
 | 
			
		||||
      result_keys_to_origins_map).get_msg();
 | 
			
		||||
 | 
			
		||||
    // finish account update
 | 
			
		||||
    finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map));
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
							
								
								
									
										290
									
								
								src/multisig/multisig_kex_msg.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								src/multisig/multisig_kex_msg.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,290 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
//
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
//
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
//
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
//
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
//
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#include "multisig_kex_msg.h"
 | 
			
		||||
#include "multisig_kex_msg_serialization.h"
 | 
			
		||||
 | 
			
		||||
#include "common/base58.h"
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
extern "C"
 | 
			
		||||
{
 | 
			
		||||
#include "crypto/crypto-ops.h"
 | 
			
		||||
}
 | 
			
		||||
#include "cryptonote_basic/cryptonote_format_utils.h"
 | 
			
		||||
#include "include_base_utils.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
#include "serialization/binary_archive.h"
 | 
			
		||||
#include "serialization/serialization.h"
 | 
			
		||||
 | 
			
		||||
#include <boost/utility/string_ref.hpp> 
 | 
			
		||||
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#undef MONERO_DEFAULT_LOG_CATEGORY
 | 
			
		||||
#define MONERO_DEFAULT_LOG_CATEGORY "multisig"
 | 
			
		||||
 | 
			
		||||
const boost::string_ref MULTISIG_KEX_V1_MAGIC{"MultisigV1"};
 | 
			
		||||
const boost::string_ref MULTISIG_KEX_MSG_V1_MAGIC{"MultisigxV1"};
 | 
			
		||||
const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_1{"MultisigxV2R1"};  //round 1
 | 
			
		||||
const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_N{"MultisigxV2Rn"};  //round n > 1
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_kex_msg: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  multisig_kex_msg::multisig_kex_msg(const std::uint32_t round,
 | 
			
		||||
    const crypto::secret_key &signing_privkey,
 | 
			
		||||
    std::vector<crypto::public_key> msg_pubkeys,
 | 
			
		||||
    const crypto::secret_key &msg_privkey) :
 | 
			
		||||
      m_kex_round{round}
 | 
			
		||||
  {
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(round > 0, "Kex round must be > 0.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&signing_privkey) == 0 &&
 | 
			
		||||
      signing_privkey != crypto::null_skey, "Invalid msg signing key.");
 | 
			
		||||
 | 
			
		||||
    if (round == 1)
 | 
			
		||||
    {
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&msg_privkey) == 0 &&
 | 
			
		||||
        msg_privkey != crypto::null_skey, "Invalid msg privkey.");
 | 
			
		||||
 | 
			
		||||
      m_msg_privkey = msg_privkey;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      for (const auto &pubkey : msg_pubkeys)
 | 
			
		||||
      {
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()),
 | 
			
		||||
          "Pubkey for message was invalid.");
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES((rct::scalarmultKey(rct::pk2rct(pubkey), rct::curveOrder()) == rct::identity()),
 | 
			
		||||
          "Pubkey for message was not in prime subgroup.");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      m_msg_pubkeys = std::move(msg_pubkeys);
 | 
			
		||||
    }
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signing_privkey, m_signing_pubkey),
 | 
			
		||||
      "Failed to derive public key");
 | 
			
		||||
 | 
			
		||||
    // sets message and signing pub key
 | 
			
		||||
    construct_msg(signing_privkey);
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_kex_msg: EXTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  multisig_kex_msg::multisig_kex_msg(std::string msg) : m_msg{std::move(msg)}
 | 
			
		||||
  {
 | 
			
		||||
    parse_and_validate_msg();
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_kex_msg: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  crypto::hash multisig_kex_msg::get_msg_to_sign() const
 | 
			
		||||
  {
 | 
			
		||||
    ////
 | 
			
		||||
    // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
 | 
			
		||||
    // sign_msg = versioning-domain-sep | msg_content
 | 
			
		||||
    ///
 | 
			
		||||
 | 
			
		||||
    std::string data;
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(),
 | 
			
		||||
      "Multisig kex msg magic inconsistency.");
 | 
			
		||||
    data.reserve(MULTISIG_KEX_MSG_V2_MAGIC_1.size() + 4 + 32*(1 + (m_kex_round == 1 ? 1 : 0) + m_msg_pubkeys.size()));
 | 
			
		||||
 | 
			
		||||
    // versioning domain-sep
 | 
			
		||||
    if (m_kex_round == 1)
 | 
			
		||||
      data.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size());
 | 
			
		||||
    else
 | 
			
		||||
      data.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size());
 | 
			
		||||
 | 
			
		||||
    // kex_round as little-endian bytes
 | 
			
		||||
    for (std::size_t i{0}; i < 4; ++i)
 | 
			
		||||
    {
 | 
			
		||||
      data += static_cast<char>(m_kex_round >> i*8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // signing pubkey
 | 
			
		||||
    data.append((const char *)&m_signing_pubkey, sizeof(crypto::public_key));
 | 
			
		||||
 | 
			
		||||
    // add msg privkey if kex_round == 1
 | 
			
		||||
    if (m_kex_round == 1)
 | 
			
		||||
      data.append((const char *)&m_msg_privkey, sizeof(crypto::secret_key));
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // only add pubkeys if not round 1
 | 
			
		||||
 | 
			
		||||
      // msg pubkeys
 | 
			
		||||
      for (const auto &key : m_msg_pubkeys)
 | 
			
		||||
        data.append((const char *)&key, sizeof(crypto::public_key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // message to sign
 | 
			
		||||
    crypto::hash hash;
 | 
			
		||||
    crypto::cn_fast_hash(data.data(), data.size(), hash);
 | 
			
		||||
 | 
			
		||||
    return hash;
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_kex_msg: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_kex_msg::construct_msg(const crypto::secret_key &signing_privkey)
 | 
			
		||||
  {
 | 
			
		||||
    ////
 | 
			
		||||
    // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
 | 
			
		||||
    // sign_msg = versioning-domain-sep | msg_content
 | 
			
		||||
    // msg = versioning-domain-sep | serialize(msg_content | crypto_sig[signing_privkey](sign_msg))
 | 
			
		||||
    ///
 | 
			
		||||
 | 
			
		||||
    // sign the message
 | 
			
		||||
    crypto::signature msg_signature;
 | 
			
		||||
    crypto::hash msg_to_sign{get_msg_to_sign()};
 | 
			
		||||
    crypto::generate_signature(msg_to_sign, m_signing_pubkey, signing_privkey, msg_signature);
 | 
			
		||||
 | 
			
		||||
    // assemble the message
 | 
			
		||||
    m_msg.clear();
 | 
			
		||||
 | 
			
		||||
    std::stringstream serialized_msg_ss;
 | 
			
		||||
    binary_archive<true> b_archive(serialized_msg_ss);
 | 
			
		||||
 | 
			
		||||
    if (m_kex_round == 1)
 | 
			
		||||
    {
 | 
			
		||||
      m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size());
 | 
			
		||||
 | 
			
		||||
      multisig_kex_msg_serializable_round1 msg_serializable;
 | 
			
		||||
      msg_serializable.msg_privkey    = m_msg_privkey;
 | 
			
		||||
      msg_serializable.signing_pubkey = m_signing_pubkey;
 | 
			
		||||
      msg_serializable.signature      = msg_signature;
 | 
			
		||||
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable),
 | 
			
		||||
        "Failed to serialize multisig kex msg");
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size());
 | 
			
		||||
 | 
			
		||||
      multisig_kex_msg_serializable_general msg_serializable;
 | 
			
		||||
      msg_serializable.kex_round      = m_kex_round;
 | 
			
		||||
      msg_serializable.msg_pubkeys    = m_msg_pubkeys;
 | 
			
		||||
      msg_serializable.signing_pubkey = m_signing_pubkey;
 | 
			
		||||
      msg_serializable.signature      = msg_signature;
 | 
			
		||||
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable),
 | 
			
		||||
        "Failed to serialize multisig kex msg");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_msg.append(tools::base58::encode(serialized_msg_ss.str()));
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  // multisig_kex_msg: INTERNAL
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  void multisig_kex_msg::parse_and_validate_msg()
 | 
			
		||||
  {
 | 
			
		||||
    // check message type
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC,
 | 
			
		||||
      "V1 multisig kex messages are deprecated (unsafe).");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC,
 | 
			
		||||
      "V1 multisig kex messages are deprecated (unsafe).");
 | 
			
		||||
 | 
			
		||||
    // deserialize the message
 | 
			
		||||
    std::string msg_no_magic;
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(),
 | 
			
		||||
      "Multisig kex msg magic inconsistency.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic),
 | 
			
		||||
      "Multisig kex msg decoding error.");
 | 
			
		||||
    binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)};
 | 
			
		||||
    crypto::signature msg_signature;
 | 
			
		||||
 | 
			
		||||
    if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_1.size()) == MULTISIG_KEX_MSG_V2_MAGIC_1)
 | 
			
		||||
    {
 | 
			
		||||
      // try round 1 message
 | 
			
		||||
      multisig_kex_msg_serializable_round1 kex_msg_rnd1;
 | 
			
		||||
 | 
			
		||||
      if (::serialization::serialize(b_archive, kex_msg_rnd1))
 | 
			
		||||
      {
 | 
			
		||||
        // in round 1 the message stores a private ancillary key component for the multisig account
 | 
			
		||||
        // that will be shared by all participants (e.g. a shared private view key)
 | 
			
		||||
        m_kex_round      = 1;
 | 
			
		||||
        m_msg_privkey    = kex_msg_rnd1.msg_privkey;
 | 
			
		||||
        m_signing_pubkey = kex_msg_rnd1.signing_pubkey;
 | 
			
		||||
        msg_signature    = kex_msg_rnd1.signature;
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_N.size()) == MULTISIG_KEX_MSG_V2_MAGIC_N)
 | 
			
		||||
    {
 | 
			
		||||
      // try general message
 | 
			
		||||
      multisig_kex_msg_serializable_general kex_msg_general;
 | 
			
		||||
 | 
			
		||||
      if (::serialization::serialize(b_archive, kex_msg_general))
 | 
			
		||||
      {
 | 
			
		||||
        m_kex_round      = kex_msg_general.kex_round;
 | 
			
		||||
        m_msg_privkey    = crypto::null_skey;
 | 
			
		||||
        m_msg_pubkeys    = std::move(kex_msg_general.msg_pubkeys);
 | 
			
		||||
        m_signing_pubkey = kex_msg_general.signing_pubkey;
 | 
			
		||||
        msg_signature    = kex_msg_general.signature;
 | 
			
		||||
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES(m_kex_round > 1, "Invalid kex message round (must be > 1 for the general msg type).");
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed.");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // unknown message type
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(false, "Only v2 multisig kex messages are supported.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks
 | 
			
		||||
    for (const auto &pubkey: m_msg_pubkeys)
 | 
			
		||||
    {
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()),
 | 
			
		||||
        "Pubkey from message was invalid.");
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(pubkey)),
 | 
			
		||||
        "Pubkey from message was not in prime subgroup.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(m_signing_pubkey != crypto::null_pkey && m_signing_pubkey != rct::rct2pk(rct::identity()),
 | 
			
		||||
      "Message signing key was invalid.");
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(m_signing_pubkey)),
 | 
			
		||||
      "Message signing key was not in prime subgroup.");
 | 
			
		||||
 | 
			
		||||
    // validate signature
 | 
			
		||||
    crypto::hash signed_msg{get_msg_to_sign()};
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::check_signature(signed_msg, m_signing_pubkey, msg_signature),
 | 
			
		||||
      "Multisig kex msg signature invalid.");
 | 
			
		||||
  }
 | 
			
		||||
  //----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
							
								
								
									
										109
									
								
								src/multisig/multisig_kex_msg.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/multisig/multisig_kex_msg.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,109 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
// 
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
// 
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
// 
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
// 
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
// 
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
// 
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  ////
 | 
			
		||||
  // multisig key exchange message
 | 
			
		||||
  // - can parse and validate an input message
 | 
			
		||||
  // - can construct and sign a new message
 | 
			
		||||
  //
 | 
			
		||||
  // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
 | 
			
		||||
  // msg_to_sign = versioning-domain-sep | msg_content
 | 
			
		||||
  // msg = versioning-domain-sep | b58(msg_content | crypto_sig[signing_privkey](msg_to_sign))
 | 
			
		||||
  //
 | 
			
		||||
  // note: round 1 messages will contain a private key (e.g. for the aggregate multisig private view key)
 | 
			
		||||
  ///
 | 
			
		||||
  class multisig_kex_msg final
 | 
			
		||||
  {
 | 
			
		||||
  //member types: none
 | 
			
		||||
 | 
			
		||||
  //constructors
 | 
			
		||||
  public:
 | 
			
		||||
    // default constructor
 | 
			
		||||
    multisig_kex_msg() = default;
 | 
			
		||||
 | 
			
		||||
    // construct from info
 | 
			
		||||
    multisig_kex_msg(const std::uint32_t round,
 | 
			
		||||
      const crypto::secret_key &signing_privkey,
 | 
			
		||||
      std::vector<crypto::public_key> msg_pubkeys,
 | 
			
		||||
      const crypto::secret_key &msg_privkey = crypto::null_skey);
 | 
			
		||||
 | 
			
		||||
    // construct from string
 | 
			
		||||
    multisig_kex_msg(std::string msg);
 | 
			
		||||
 | 
			
		||||
    // copy constructor: default
 | 
			
		||||
 | 
			
		||||
  //destructor: default
 | 
			
		||||
    ~multisig_kex_msg() = default;
 | 
			
		||||
 | 
			
		||||
  //overloaded operators: none
 | 
			
		||||
 | 
			
		||||
  //member functions
 | 
			
		||||
    // get msg string
 | 
			
		||||
    const std::string& get_msg() const { return m_msg; }
 | 
			
		||||
    // get kex round
 | 
			
		||||
    std::uint32_t get_round() const { return m_kex_round; }
 | 
			
		||||
    // get msg pubkeys
 | 
			
		||||
    const std::vector<crypto::public_key>& get_msg_pubkeys() const { return m_msg_pubkeys; }
 | 
			
		||||
    // get msg privkey
 | 
			
		||||
    const crypto::secret_key& get_msg_privkey() const { return m_msg_privkey; }
 | 
			
		||||
    // get msg signing pubkey
 | 
			
		||||
    const crypto::public_key& get_signing_pubkey() const { return m_signing_pubkey; }
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    // msg_to_sign = versioning-domain-sep | kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey
 | 
			
		||||
    crypto::hash get_msg_to_sign() const;
 | 
			
		||||
    // set: msg string based on msg contents, signing pubkey based on input privkey
 | 
			
		||||
    void construct_msg(const crypto::secret_key &signing_privkey);
 | 
			
		||||
    // parse msg string into parts, validate contents and signature
 | 
			
		||||
    void parse_and_validate_msg();
 | 
			
		||||
 | 
			
		||||
  //member variables
 | 
			
		||||
  private:
 | 
			
		||||
    // message as string
 | 
			
		||||
    std::string m_msg;
 | 
			
		||||
 | 
			
		||||
    // key exchange round this msg was produced for
 | 
			
		||||
    std::uint32_t m_kex_round;
 | 
			
		||||
    // pubkeys stored in msg
 | 
			
		||||
    std::vector<crypto::public_key> m_msg_pubkeys;
 | 
			
		||||
    // privkey stored in msg (if kex round 1)
 | 
			
		||||
    crypto::secret_key m_msg_privkey;
 | 
			
		||||
    // pubkey used to sign this msg
 | 
			
		||||
    crypto::public_key m_signing_pubkey;
 | 
			
		||||
  };
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
							
								
								
									
										78
									
								
								src/multisig/multisig_kex_msg_serialization.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/multisig/multisig_kex_msg_serialization.h
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
// Copyright (c) 2021, The Monero Project
 | 
			
		||||
// 
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
// 
 | 
			
		||||
// Redistribution and use in source and binary forms, with or without modification, are
 | 
			
		||||
// permitted provided that the following conditions are met:
 | 
			
		||||
// 
 | 
			
		||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
 | 
			
		||||
//    conditions and the following disclaimer.
 | 
			
		||||
// 
 | 
			
		||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
 | 
			
		||||
//    of conditions and the following disclaimer in the documentation and/or other
 | 
			
		||||
//    materials provided with the distribution.
 | 
			
		||||
// 
 | 
			
		||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
 | 
			
		||||
//    used to endorse or promote products derived from this software without specific
 | 
			
		||||
//    prior written permission.
 | 
			
		||||
// 
 | 
			
		||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 | 
			
		||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 | 
			
		||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "serialization/containers.h"
 | 
			
		||||
#include "serialization/crypto.h"
 | 
			
		||||
#include "serialization/serialization.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace multisig
 | 
			
		||||
{
 | 
			
		||||
  /// round 1 kex message
 | 
			
		||||
  struct multisig_kex_msg_serializable_round1
 | 
			
		||||
  {
 | 
			
		||||
    // privkey stored in msg
 | 
			
		||||
    crypto::secret_key msg_privkey;
 | 
			
		||||
    // pubkey used to sign this msg
 | 
			
		||||
    crypto::public_key signing_pubkey;
 | 
			
		||||
    // message signature
 | 
			
		||||
    crypto::signature signature;
 | 
			
		||||
 | 
			
		||||
    BEGIN_SERIALIZE()
 | 
			
		||||
      FIELD(msg_privkey)
 | 
			
		||||
      FIELD(signing_pubkey)
 | 
			
		||||
      FIELD(signature)
 | 
			
		||||
    END_SERIALIZE()
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /// general kex message (if round > 1)
 | 
			
		||||
  struct multisig_kex_msg_serializable_general
 | 
			
		||||
  {
 | 
			
		||||
    // key exchange round this msg was produced for
 | 
			
		||||
    std::uint32_t kex_round;
 | 
			
		||||
    // pubkeys stored in msg
 | 
			
		||||
    std::vector<crypto::public_key> msg_pubkeys;
 | 
			
		||||
    // pubkey used to sign this msg
 | 
			
		||||
    crypto::public_key signing_pubkey;
 | 
			
		||||
    // message signature
 | 
			
		||||
    crypto::signature signature;
 | 
			
		||||
 | 
			
		||||
    BEGIN_SERIALIZE()
 | 
			
		||||
      VARINT_FIELD(kex_round)
 | 
			
		||||
      FIELD(msg_pubkeys)
 | 
			
		||||
      FIELD(signing_pubkey)
 | 
			
		||||
      FIELD(signature)
 | 
			
		||||
    END_SERIALIZE()
 | 
			
		||||
  };
 | 
			
		||||
} //namespace multisig
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +235,6 @@ namespace
 | 
			
		|||
  const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>");
 | 
			
		||||
  const char* USAGE_SHOW_TRANSFER("show_transfer <txid>");
 | 
			
		||||
  const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]");
 | 
			
		||||
  const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]");
 | 
			
		||||
  const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]");
 | 
			
		||||
  const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>");
 | 
			
		||||
  const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]");
 | 
			
		||||
| 
						 | 
				
			
			@ -1021,7 +1020,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args,
 | 
			
		|||
 | 
			
		||||
  SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;);
 | 
			
		||||
 | 
			
		||||
  std::string multisig_info = m_wallet->get_multisig_info();
 | 
			
		||||
  std::string multisig_info = m_wallet->get_multisig_first_kex_msg();
 | 
			
		||||
  success_msg_writer() << multisig_info;
 | 
			
		||||
  success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info");
 | 
			
		||||
  success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants ");
 | 
			
		||||
| 
						 | 
				
			
			@ -1122,58 +1121,6 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo
 | 
			
		|||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
 | 
			
		||||
{
 | 
			
		||||
  bool ready;
 | 
			
		||||
  if (m_wallet->key_on_device())
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("command not supported by HW wallet");
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const auto pwd_container = get_and_verify_password();
 | 
			
		||||
  if(pwd_container == boost::none)
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("Your original password was incorrect.");
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!m_wallet->multisig(&ready))
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("This wallet is not multisig");
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  if (ready)
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("This wallet is already finalized");
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  LOCK_IDLE_SCOPE();
 | 
			
		||||
 | 
			
		||||
  if (args.size() < 2)
 | 
			
		||||
  {
 | 
			
		||||
    PRINT_USAGE(USAGE_FINALIZE_MULTISIG);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try
 | 
			
		||||
  {
 | 
			
		||||
    if (!m_wallet->finalize_multisig(pwd_container->password(), args))
 | 
			
		||||
    {
 | 
			
		||||
      fail_msg_writer() << tr("Failed to finalize multisig");
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  catch (const std::exception &e)
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args)
 | 
			
		||||
{
 | 
			
		||||
  exchange_multisig_keys_main(args, false);
 | 
			
		||||
| 
						 | 
				
			
			@ -3627,10 +3574,6 @@ simple_wallet::simple_wallet()
 | 
			
		|||
  m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1),
 | 
			
		||||
                           tr(USAGE_MAKE_MULTISIG),
 | 
			
		||||
                           tr("Turn this wallet into a multisig wallet"));
 | 
			
		||||
  m_cmd_binder.set_handler("finalize_multisig",
 | 
			
		||||
                           boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1),
 | 
			
		||||
                           tr(USAGE_FINALIZE_MULTISIG),
 | 
			
		||||
                           tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets"));
 | 
			
		||||
  m_cmd_binder.set_handler("exchange_multisig_keys",
 | 
			
		||||
                           boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1),
 | 
			
		||||
                           tr(USAGE_EXCHANGE_MULTISIG_KEYS),
 | 
			
		||||
| 
						 | 
				
			
			@ -10973,8 +10916,8 @@ void simple_wallet::mms_init(const std::vector<std::string> &args)
 | 
			
		|||
  std::vector<std::string> numbers;
 | 
			
		||||
  boost::split(numbers, mn, boost::is_any_of("/"));
 | 
			
		||||
  bool mn_ok = (numbers.size() == 2)
 | 
			
		||||
               && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100)
 | 
			
		||||
               && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers);
 | 
			
		||||
               && get_number_from_arg(numbers[1], num_authorized_signers, 2, config::MULTISIG_MAX_SIGNERS)
 | 
			
		||||
               && get_number_from_arg(numbers[0], num_required_signers, 1, num_authorized_signers);
 | 
			
		||||
  if (!mn_ok)
 | 
			
		||||
  {
 | 
			
		||||
    fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -233,7 +233,6 @@ namespace cryptonote
 | 
			
		|||
    bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
 | 
			
		||||
    bool make_multisig(const std::vector<std::string>& args);
 | 
			
		||||
    bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms);
 | 
			
		||||
    bool finalize_multisig(const std::vector<std::string> &args);
 | 
			
		||||
    bool exchange_multisig_keys(const std::vector<std::string> &args);
 | 
			
		||||
    bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms);
 | 
			
		||||
    bool export_multisig(const std::vector<std::string>& args);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1332,7 +1332,7 @@ MultisigState WalletImpl::multisig() const {
 | 
			
		|||
string WalletImpl::getMultisigInfo() const {
 | 
			
		||||
    try {
 | 
			
		||||
        clearStatus();
 | 
			
		||||
        return m_wallet->get_multisig_info();
 | 
			
		||||
        return m_wallet->get_multisig_first_kex_msg();
 | 
			
		||||
    } catch (const exception& e) {
 | 
			
		||||
        LOG_ERROR("Error on generating multisig info: " << e.what());
 | 
			
		||||
        setStatusError(string(tr("Failed to get multisig info: ")) + e.what());
 | 
			
		||||
| 
						 | 
				
			
			@ -1341,7 +1341,7 @@ string WalletImpl::getMultisigInfo() const {
 | 
			
		|||
    return string();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) {
 | 
			
		||||
string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) {
 | 
			
		||||
    try {
 | 
			
		||||
        clearStatus();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1366,30 +1366,12 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf
 | 
			
		|||
        return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info);
 | 
			
		||||
    } catch (const exception& e) {
 | 
			
		||||
        LOG_ERROR("Error on exchanging multisig keys: " << e.what());
 | 
			
		||||
        setStatusError(string(tr("Failed to make multisig: ")) + e.what());
 | 
			
		||||
        setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return string();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) {
 | 
			
		||||
    try {
 | 
			
		||||
        clearStatus();
 | 
			
		||||
        checkMultisigWalletNotReady(m_wallet);
 | 
			
		||||
 | 
			
		||||
        if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setStatusError(tr("Failed to finalize multisig wallet creation"));
 | 
			
		||||
    } catch (const exception& e) {
 | 
			
		||||
        LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what());
 | 
			
		||||
        setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool WalletImpl::exportMultisigImages(string& images) {
 | 
			
		||||
    try {
 | 
			
		||||
        clearStatus();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -147,7 +147,6 @@ public:
 | 
			
		|||
    std::string getMultisigInfo() const override;
 | 
			
		||||
    std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override;
 | 
			
		||||
    std::string exchangeMultisigKeys(const std::vector<std::string> &info) override;
 | 
			
		||||
    bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override;
 | 
			
		||||
    bool exportMultisigImages(std::string& images) override;
 | 
			
		||||
    size_t importMultisigImages(const std::vector<std::string>& images) override;
 | 
			
		||||
    bool hasMultisigPartialKeyImages() const override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -790,7 +790,7 @@ struct Wallet
 | 
			
		|||
    /**
 | 
			
		||||
     * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets
 | 
			
		||||
     * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call
 | 
			
		||||
     * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1
 | 
			
		||||
     * @param threshold - number of required signers to make valid transaction. Must be <= number of participants
 | 
			
		||||
     * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -800,12 +800,6 @@ struct Wallet
 | 
			
		|||
     * @return new info string if more rounds required or an empty string if wallet creation is done
 | 
			
		||||
     */
 | 
			
		||||
    virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0;
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation
 | 
			
		||||
     * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call
 | 
			
		||||
     * @return true if success
 | 
			
		||||
     */
 | 
			
		||||
    virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0;
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief exportMultisigImages - exports transfers' key images
 | 
			
		||||
     * @param images - output paramter for hex encoded array of images
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@
 | 
			
		|||
// 
 | 
			
		||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <queue>
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +60,8 @@ using namespace epee;
 | 
			
		|||
#include "misc_language.h"
 | 
			
		||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
 | 
			
		||||
#include "multisig/multisig.h"
 | 
			
		||||
#include "multisig/multisig_account.h"
 | 
			
		||||
#include "multisig/multisig_kex_msg.h"
 | 
			
		||||
#include "common/boost_serialization_helper.h"
 | 
			
		||||
#include "common/command_line.h"
 | 
			
		||||
#include "common/threadpool.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +152,6 @@ using namespace cryptonote;
 | 
			
		|||
#define RECENT_SPEND_WINDOW (50 * DIFFICULTY_TARGET_V2)
 | 
			
		||||
 | 
			
		||||
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
 | 
			
		||||
static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
 | 
			
		||||
 | 
			
		||||
static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -167,42 +169,6 @@ namespace
 | 
			
		|||
    return dir.string();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key)
 | 
			
		||||
  {
 | 
			
		||||
    std::string data;
 | 
			
		||||
    crypto::public_key signer;
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key");
 | 
			
		||||
    data += std::string((const char *)&signer, sizeof(crypto::public_key));
 | 
			
		||||
 | 
			
		||||
    for (const auto &key: keys)
 | 
			
		||||
    {
 | 
			
		||||
      data += std::string((const char *)&key, sizeof(crypto::public_key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data.resize(data.size() + sizeof(crypto::signature));
 | 
			
		||||
 | 
			
		||||
    crypto::hash hash;
 | 
			
		||||
    crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash);
 | 
			
		||||
    crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
 | 
			
		||||
    crypto::generate_signature(hash, signer, signer_secret_key, signature);
 | 
			
		||||
 | 
			
		||||
    return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys)
 | 
			
		||||
  {
 | 
			
		||||
    std::vector<crypto::public_key> public_keys;
 | 
			
		||||
    public_keys.reserve(keys.size());
 | 
			
		||||
 | 
			
		||||
    std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key {
 | 
			
		||||
      crypto::public_key p;
 | 
			
		||||
      CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key");
 | 
			
		||||
      return p;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return public_keys;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2)
 | 
			
		||||
  {
 | 
			
		||||
    if (s1.empty() || s2.empty())
 | 
			
		||||
| 
						 | 
				
			
			@ -4768,7 +4734,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
 | 
			
		|||
  memwipe(&skey, sizeof(rct::key));
 | 
			
		||||
 | 
			
		||||
  m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys);
 | 
			
		||||
  m_account.finalize_multisig(spend_public_key);
 | 
			
		||||
 | 
			
		||||
  // Not possible to restore a multisig wallet that is able to activate the MMS
 | 
			
		||||
  // (because the original keys are not (yet) part of the restore info), so
 | 
			
		||||
| 
						 | 
				
			
			@ -4983,24 +4948,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
 | 
			
		|||
    store();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
std::string wallet2::make_multisig(const epee::wipeable_string &password,
 | 
			
		||||
  const std::vector<crypto::secret_key> &view_keys,
 | 
			
		||||
  const std::vector<crypto::public_key> &spend_keys,
 | 
			
		||||
  uint32_t threshold)
 | 
			
		||||
  const std::vector<std::string> &initial_kex_msgs,
 | 
			
		||||
  const std::uint32_t threshold)
 | 
			
		||||
{
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold");
 | 
			
		||||
 | 
			
		||||
  std::string extra_multisig_info;
 | 
			
		||||
  std::vector<crypto::secret_key> multisig_keys;
 | 
			
		||||
  rct::key spend_pkey = rct::identity();
 | 
			
		||||
  rct::key spend_skey;
 | 
			
		||||
  auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(&spend_skey, sizeof(spend_skey));});
 | 
			
		||||
  std::vector<crypto::public_key> multisig_signers;
 | 
			
		||||
 | 
			
		||||
  // decrypt keys
 | 
			
		||||
  // decrypt account keys
 | 
			
		||||
  epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
 | 
			
		||||
  if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			@ -5008,104 +4961,89 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
 | 
			
		|||
    crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
 | 
			
		||||
    m_account.encrypt_viewkey(chacha_key);
 | 
			
		||||
    m_account.decrypt_keys(chacha_key);
 | 
			
		||||
    keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
 | 
			
		||||
    keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
 | 
			
		||||
        [&, this, chacha_key]()
 | 
			
		||||
        {
 | 
			
		||||
          m_account.encrypt_keys(chacha_key);
 | 
			
		||||
          m_account.decrypt_viewkey(chacha_key);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // In common multisig scheme there are 4 types of key exchange rounds:
 | 
			
		||||
  // 1. First round is exchange of view secret keys and public spend keys.
 | 
			
		||||
  // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key,
 | 
			
		||||
  //    M - public multisig key (in first round it equals to public spend key), K - new public multisig key.
 | 
			
		||||
  // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key,
 | 
			
		||||
  //    k - secret multisig key used to sign transactions. k and M are sets of keys, of course.
 | 
			
		||||
  //    And secret spend key as the sum of all participant's secret multisig keys
 | 
			
		||||
  // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys
 | 
			
		||||
  //    and calculate common spend public key as sum of all unique participants' public multisig keys.
 | 
			
		||||
  // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds.
 | 
			
		||||
  // create multisig account
 | 
			
		||||
  multisig::multisig_account multisig_account{
 | 
			
		||||
      multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
 | 
			
		||||
      multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G!
 | 
			
		||||
  // Wallet's public spend key is the sum of unique public multisig keys of all participants.
 | 
			
		||||
  // secret_spend_key * G = public signer key
 | 
			
		||||
  // open initial kex messages, validate them, extract signers
 | 
			
		||||
  std::vector<multisig::multisig_kex_msg> expanded_msgs;
 | 
			
		||||
  std::vector<crypto::public_key> signers;
 | 
			
		||||
  expanded_msgs.reserve(initial_kex_msgs.size());
 | 
			
		||||
  signers.reserve(initial_kex_msgs.size() + 1);
 | 
			
		||||
 | 
			
		||||
  if (threshold == spend_keys.size() + 1)
 | 
			
		||||
  for (const auto &msg : initial_kex_msgs)
 | 
			
		||||
  {
 | 
			
		||||
    // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key
 | 
			
		||||
    MINFO("Creating spend key...");
 | 
			
		||||
    expanded_msgs.emplace_back(msg);
 | 
			
		||||
 | 
			
		||||
    // Calculates all multisig keys and spend key
 | 
			
		||||
    cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey);
 | 
			
		||||
    // validate each message
 | 
			
		||||
    // 1. must be 'round 1'
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1,
 | 
			
		||||
      "Trying to make multisig with message that has invalid multisig kex round (should be '1').");
 | 
			
		||||
 | 
			
		||||
    // Our signer key is b * G, where b is secret spend key.
 | 
			
		||||
    multisig_signers = spend_keys;
 | 
			
		||||
    multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key));
 | 
			
		||||
    // 2. duplicate signers not allowed
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(),
 | 
			
		||||
      "Duplicate signers not allowed when converting a wallet to multisig.");
 | 
			
		||||
 | 
			
		||||
    // add signer (skip self for now)
 | 
			
		||||
    if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey())
 | 
			
		||||
      signers.push_back(expanded_msgs.back().get_signing_pubkey());
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi).
 | 
			
		||||
    // note that derivations are public keys as DH exchange suppose it to be
 | 
			
		||||
    auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys);
 | 
			
		||||
 | 
			
		||||
    spend_pkey = rct::identity();
 | 
			
		||||
    multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
 | 
			
		||||
  // add self to signers
 | 
			
		||||
  signers.push_back(multisig_account.get_base_pubkey());
 | 
			
		||||
 | 
			
		||||
    if (threshold == spend_keys.size())
 | 
			
		||||
    {
 | 
			
		||||
      // N - 1 / N case
 | 
			
		||||
  // intialize key exchange
 | 
			
		||||
  multisig_account.initialize_kex(threshold, signers, expanded_msgs);
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(multisig_account.account_is_active(), "Failed to activate multisig account.");
 | 
			
		||||
 | 
			
		||||
      // We need an extra step, so we package all the composite public keys
 | 
			
		||||
      // we know about, and make a signed string out of them
 | 
			
		||||
      MINFO("Creating spend key...");
 | 
			
		||||
 | 
			
		||||
      // Calculating set of our secret multisig keys as follows: mi = H(Mi),
 | 
			
		||||
      // where mi - secret multisig key, Mi - others' participants public multisig key
 | 
			
		||||
      multisig_keys = cryptonote::calculate_multisig_keys(derivations);
 | 
			
		||||
 | 
			
		||||
      // calculating current participant's spend secret key as sum of all secret multisig keys for current participant.
 | 
			
		||||
      // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend!
 | 
			
		||||
      //            Entire wallet's secret spend is sum of all unique secret multisig keys
 | 
			
		||||
      //            among all of participants and is not held by anyone!
 | 
			
		||||
      spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys));
 | 
			
		||||
 | 
			
		||||
      // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys.
 | 
			
		||||
      extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey));
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      // M / N case
 | 
			
		||||
      MINFO("Preparing keys for next exchange round...");
 | 
			
		||||
 | 
			
		||||
      // Preparing data for middle round - packing new public multisig keys to exchage with others.
 | 
			
		||||
      extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key);
 | 
			
		||||
      spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key);
 | 
			
		||||
 | 
			
		||||
      // Need to store middle keys to be able to proceed in case of wallet shutdown.
 | 
			
		||||
      m_multisig_derivations = derivations;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // update wallet state
 | 
			
		||||
  if (!m_original_keys_available)
 | 
			
		||||
  {
 | 
			
		||||
    // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages
 | 
			
		||||
    // (making a wallet multisig overwrites those keys, see account_base::make_multisig)
 | 
			
		||||
    m_original_address = m_account.get_keys().m_account_address;
 | 
			
		||||
    m_original_view_secret_key = m_account.get_keys().m_view_secret_key;
 | 
			
		||||
    m_original_address = get_account().get_keys().m_account_address;
 | 
			
		||||
    m_original_view_secret_key = get_account().get_keys().m_view_secret_key;
 | 
			
		||||
    m_original_keys_available = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clear();
 | 
			
		||||
  MINFO("Creating view key...");
 | 
			
		||||
  crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys);
 | 
			
		||||
 | 
			
		||||
  // account base
 | 
			
		||||
  MINFO("Creating multisig address...");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys),
 | 
			
		||||
      "Failed to create multisig wallet due to bad keys");
 | 
			
		||||
  memwipe(&spend_skey, sizeof(rct::key));
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(),
 | 
			
		||||
    multisig_account.get_base_privkey(),
 | 
			
		||||
    multisig_account.get_multisig_pubkey(),
 | 
			
		||||
    multisig_account.get_multisig_privkeys()),
 | 
			
		||||
      "Failed to create multisig wallet account due to bad keys");
 | 
			
		||||
 | 
			
		||||
  init_type(hw::device::device_type::SOFTWARE);
 | 
			
		||||
  m_original_keys_available = true;
 | 
			
		||||
  m_multisig = true;
 | 
			
		||||
  m_multisig_threshold = threshold;
 | 
			
		||||
  m_multisig_signers = multisig_signers;
 | 
			
		||||
  ++m_multisig_rounds_passed;
 | 
			
		||||
  m_multisig_signers = signers;
 | 
			
		||||
  m_multisig_rounds_passed = 1;
 | 
			
		||||
 | 
			
		||||
  // derivations stored (should be empty in last round)
 | 
			
		||||
  // TODO: make use of the origins map for aggregation-style signing (instead of round-robin)
 | 
			
		||||
  m_multisig_derivations.clear();
 | 
			
		||||
  m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
 | 
			
		||||
 | 
			
		||||
  for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map())
 | 
			
		||||
    m_multisig_derivations.push_back(key_to_origins.first);
 | 
			
		||||
 | 
			
		||||
  // address
 | 
			
		||||
  m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey();
 | 
			
		||||
 | 
			
		||||
  // re-encrypt keys
 | 
			
		||||
  keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
 | 
			
		||||
| 
						 | 
				
			
			@ -5118,42 +5056,18 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
 | 
			
		|||
  if (!m_wallet_file.empty())
 | 
			
		||||
    store();
 | 
			
		||||
 | 
			
		||||
  return extra_multisig_info;
 | 
			
		||||
  return multisig_account.get_next_kex_round_msg();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
 | 
			
		||||
  const std::vector<std::string> &info)
 | 
			
		||||
  const std::vector<std::string> &kex_messages)
 | 
			
		||||
{
 | 
			
		||||
  THROW_WALLET_EXCEPTION_IF(info.empty(),
 | 
			
		||||
    error::wallet_internal_error, "Empty multisig info");
 | 
			
		||||
 | 
			
		||||
  if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
 | 
			
		||||
  {
 | 
			
		||||
    THROW_WALLET_EXCEPTION_IF(false,
 | 
			
		||||
      error::wallet_internal_error, "Unsupported info string");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<crypto::public_key> signers;
 | 
			
		||||
  std::unordered_set<crypto::public_key> pkeys;
 | 
			
		||||
 | 
			
		||||
  THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys),
 | 
			
		||||
    error::wallet_internal_error, "Bad extra multisig info");
 | 
			
		||||
 | 
			
		||||
  return exchange_multisig_keys(password, pkeys, signers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password,
 | 
			
		||||
  std::unordered_set<crypto::public_key> derivations,
 | 
			
		||||
  std::vector<crypto::public_key> signers)
 | 
			
		||||
{
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers");
 | 
			
		||||
 | 
			
		||||
  bool ready = false;
 | 
			
		||||
  bool ready{false};
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished");
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in.");
 | 
			
		||||
 | 
			
		||||
  // keys are decrypted
 | 
			
		||||
  // decrypt account keys
 | 
			
		||||
  epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
 | 
			
		||||
  if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			@ -5161,37 +5075,72 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
 | 
			
		|||
    crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
 | 
			
		||||
    m_account.encrypt_viewkey(chacha_key);
 | 
			
		||||
    m_account.decrypt_keys(chacha_key);
 | 
			
		||||
    keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
 | 
			
		||||
    keys_reencryptor = epee::misc_utils::create_scope_leave_handler(
 | 
			
		||||
        [&, this, chacha_key]()
 | 
			
		||||
        {
 | 
			
		||||
          m_account.encrypt_keys(chacha_key);
 | 
			
		||||
          m_account.decrypt_viewkey(chacha_key);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1)
 | 
			
		||||
  // open kex messages
 | 
			
		||||
  std::vector<multisig::multisig_kex_msg> expanded_msgs;
 | 
			
		||||
  expanded_msgs.reserve(kex_messages.size());
 | 
			
		||||
 | 
			
		||||
  for (const auto &msg : kex_messages)
 | 
			
		||||
    expanded_msgs.emplace_back(msg);
 | 
			
		||||
 | 
			
		||||
  // reconstruct multisig account
 | 
			
		||||
  crypto::public_key dummy;
 | 
			
		||||
  multisig::multisig_account::kex_origins_map_t kex_origins_map;
 | 
			
		||||
 | 
			
		||||
  for (const auto &derivation : m_multisig_derivations)
 | 
			
		||||
    kex_origins_map[derivation];
 | 
			
		||||
 | 
			
		||||
  multisig::multisig_account multisig_account{
 | 
			
		||||
      m_multisig_threshold,
 | 
			
		||||
      m_multisig_signers,
 | 
			
		||||
      get_account().get_keys().m_spend_secret_key,
 | 
			
		||||
      crypto::null_skey,  //base common privkey: not used
 | 
			
		||||
      get_account().get_keys().m_multisig_keys,
 | 
			
		||||
      get_account().get_keys().m_view_secret_key,
 | 
			
		||||
      m_account_public_address.m_spend_public_key,
 | 
			
		||||
      dummy,              //common pubkey: not used
 | 
			
		||||
      m_multisig_rounds_passed,
 | 
			
		||||
      std::move(kex_origins_map),
 | 
			
		||||
      ""
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // update multisig kex
 | 
			
		||||
  multisig_account.kex_update(expanded_msgs);
 | 
			
		||||
 | 
			
		||||
  // update wallet state
 | 
			
		||||
 | 
			
		||||
  // address
 | 
			
		||||
  m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey();
 | 
			
		||||
 | 
			
		||||
  // account base
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(),
 | 
			
		||||
    multisig_account.get_base_privkey(),
 | 
			
		||||
    multisig_account.get_multisig_pubkey(),
 | 
			
		||||
    multisig_account.get_multisig_privkeys()),
 | 
			
		||||
      "Failed to update multisig wallet account due to bad keys");
 | 
			
		||||
 | 
			
		||||
  // derivations stored (should be empty in last round)
 | 
			
		||||
  // TODO: make use of the origins map for aggregation-style signing (instead of round-robin)
 | 
			
		||||
  m_multisig_derivations.clear();
 | 
			
		||||
  m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size());
 | 
			
		||||
 | 
			
		||||
  for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map())
 | 
			
		||||
    m_multisig_derivations.push_back(key_to_origins.first);
 | 
			
		||||
 | 
			
		||||
  // rounds passed
 | 
			
		||||
  m_multisig_rounds_passed = multisig_account.get_kex_rounds_complete();
 | 
			
		||||
 | 
			
		||||
  // why is this necessary? who knows...
 | 
			
		||||
  if (multisig_account.multisig_is_ready())
 | 
			
		||||
  {
 | 
			
		||||
    // the last round is passed and we have to calculate spend public key
 | 
			
		||||
    // add ours if not included
 | 
			
		||||
    crypto::public_key local_signer = get_multisig_signer_public_key();
 | 
			
		||||
 | 
			
		||||
    if (std::find(signers.begin(), signers.end(), local_signer) == signers.end())
 | 
			
		||||
    {
 | 
			
		||||
        signers.push_back(local_signer);
 | 
			
		||||
        for (const auto &msk: get_account().get_multisig_keys())
 | 
			
		||||
        {
 | 
			
		||||
            derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk))));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size");
 | 
			
		||||
 | 
			
		||||
    // Summing all of unique public multisig keys to calculate common public spend key
 | 
			
		||||
    crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
 | 
			
		||||
    m_account_public_address.m_spend_public_key = spend_public_key;
 | 
			
		||||
    m_account.finalize_multisig(spend_public_key);
 | 
			
		||||
 | 
			
		||||
    m_multisig_signers = signers;
 | 
			
		||||
    std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)) < 0; });
 | 
			
		||||
 | 
			
		||||
    ++m_multisig_rounds_passed;
 | 
			
		||||
    m_multisig_derivations.clear();
 | 
			
		||||
 | 
			
		||||
    // keys are encrypted again
 | 
			
		||||
    keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5213,270 +5162,28 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor
 | 
			
		|||
 | 
			
		||||
    if (!m_wallet_file.empty())
 | 
			
		||||
      store();
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Below are either middle or secret spend key establishment rounds
 | 
			
		||||
 | 
			
		||||
  for (const auto& key: m_multisig_derivations)
 | 
			
		||||
    derivations.erase(key);
 | 
			
		||||
 | 
			
		||||
  // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys.
 | 
			
		||||
  auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end()));
 | 
			
		||||
 | 
			
		||||
  std::string extra_multisig_info;
 | 
			
		||||
  if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last
 | 
			
		||||
  {
 | 
			
		||||
    // Next round is last therefore we are performing secret spend establishment round as described above.
 | 
			
		||||
    MINFO("Creating spend key...");
 | 
			
		||||
 | 
			
		||||
    // Calculating our secret multisig keys by hashing our public multisig keys.
 | 
			
		||||
    auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end()));
 | 
			
		||||
    // And summing it to get personal secret spend key
 | 
			
		||||
    crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys);
 | 
			
		||||
 | 
			
		||||
    m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys);
 | 
			
		||||
 | 
			
		||||
    // Packing public multisig keys to exchange with others and calculate common public spend key in the last round
 | 
			
		||||
    extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    // This is just middle round
 | 
			
		||||
    MINFO("Preparing keys for next exchange round...");
 | 
			
		||||
    extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key);
 | 
			
		||||
    m_multisig_derivations = new_derivations;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ++m_multisig_rounds_passed;
 | 
			
		||||
 | 
			
		||||
  // wallet/file relationship
 | 
			
		||||
  if (!m_wallet_file.empty())
 | 
			
		||||
    create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt"));
 | 
			
		||||
 | 
			
		||||
  return extra_multisig_info;
 | 
			
		||||
  return multisig_account.get_next_kex_round_msg();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void wallet2::unpack_multisig_info(const std::vector<std::string>& info,
 | 
			
		||||
  std::vector<crypto::public_key> &public_keys,
 | 
			
		||||
  std::vector<crypto::secret_key> &secret_keys) const
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
std::string wallet2::get_multisig_first_kex_msg() const
 | 
			
		||||
{
 | 
			
		||||
  // parse all multisig info
 | 
			
		||||
  public_keys.resize(info.size());
 | 
			
		||||
  secret_keys.resize(info.size());
 | 
			
		||||
  for (size_t i = 0; i < info.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]),
 | 
			
		||||
        error::wallet_internal_error, "Bad multisig info: " + info[i]);
 | 
			
		||||
  }
 | 
			
		||||
  // create multisig account
 | 
			
		||||
  multisig::multisig_account multisig_account{
 | 
			
		||||
      // k_base = H(normal private spend key)
 | 
			
		||||
      multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key),
 | 
			
		||||
      // k_view = H(normal private view key)
 | 
			
		||||
      multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // remove duplicates
 | 
			
		||||
  for (size_t i = 0; i < secret_keys.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    for (size_t j = i + 1; j < secret_keys.size(); ++j)
 | 
			
		||||
    {
 | 
			
		||||
      if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j]))
 | 
			
		||||
      {
 | 
			
		||||
        MDEBUG("Duplicate key found, ignoring");
 | 
			
		||||
        secret_keys[j] = secret_keys.back();
 | 
			
		||||
        public_keys[j] = public_keys.back();
 | 
			
		||||
        secret_keys.pop_back();
 | 
			
		||||
        public_keys.pop_back();
 | 
			
		||||
        --j;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // people may include their own, weed it out
 | 
			
		||||
  const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
 | 
			
		||||
  const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
 | 
			
		||||
  for (size_t i = 0; i < secret_keys.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    if (secret_keys[i] == local_skey)
 | 
			
		||||
    {
 | 
			
		||||
      MDEBUG("Local key is present, ignoring");
 | 
			
		||||
      secret_keys[i] = secret_keys.back();
 | 
			
		||||
      public_keys[i] = public_keys.back();
 | 
			
		||||
      secret_keys.pop_back();
 | 
			
		||||
      public_keys.pop_back();
 | 
			
		||||
      --i;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error,
 | 
			
		||||
          "Found local spend public key, but not local view secret key - something very weird");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return multisig_account.get_next_kex_round_msg();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string wallet2::make_multisig(const epee::wipeable_string &password,
 | 
			
		||||
  const std::vector<std::string> &info,
 | 
			
		||||
  uint32_t threshold)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<crypto::secret_key> secret_keys(info.size());
 | 
			
		||||
  std::vector<crypto::public_key> public_keys(info.size());
 | 
			
		||||
  unpack_multisig_info(info, public_keys, secret_keys);
 | 
			
		||||
  return make_multisig(password, secret_keys, public_keys, threshold);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers)
 | 
			
		||||
{
 | 
			
		||||
  bool ready;
 | 
			
		||||
  uint32_t threshold, total;
 | 
			
		||||
  if (!multisig(&ready, &threshold, &total))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("This is not a multisig wallet");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (ready)
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("This multisig wallet is already finalized");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (threshold + 1 != total)
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  exchange_multisig_keys(password, pkeys, signers);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info,
 | 
			
		||||
  std::vector<crypto::public_key> &signers,
 | 
			
		||||
  std::unordered_set<crypto::public_key> &pkeys) const
 | 
			
		||||
{
 | 
			
		||||
  // parse all multisig info
 | 
			
		||||
  signers.resize(info.size(), crypto::null_pkey);
 | 
			
		||||
  for (size_t i = 0; i < info.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
      if (!verify_extra_multisig_info(info[i], pkeys, signers[i]))
 | 
			
		||||
      {
 | 
			
		||||
          return false;
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info)
 | 
			
		||||
{
 | 
			
		||||
  std::unordered_set<crypto::public_key> public_keys;
 | 
			
		||||
  std::vector<crypto::public_key> signers;
 | 
			
		||||
  if (!unpack_extra_multisig_info(info, signers, public_keys))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Bad multisig info");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return finalize_multisig(password, public_keys, signers);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string wallet2::get_multisig_info() const
 | 
			
		||||
{
 | 
			
		||||
  // It's a signed package of private view key and public spend key
 | 
			
		||||
  const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key);
 | 
			
		||||
  const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key);
 | 
			
		||||
  crypto::hash hash;
 | 
			
		||||
 | 
			
		||||
  std::string data;
 | 
			
		||||
  data += std::string((const char *)&skey, sizeof(crypto::secret_key));
 | 
			
		||||
  data += std::string((const char *)&pkey, sizeof(crypto::public_key));
 | 
			
		||||
 | 
			
		||||
  data.resize(data.size() + sizeof(crypto::signature));
 | 
			
		||||
  crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash);
 | 
			
		||||
  crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)];
 | 
			
		||||
  crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature);
 | 
			
		||||
 | 
			
		||||
  return std::string("MultisigV1") + tools::base58::encode(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey)
 | 
			
		||||
{
 | 
			
		||||
  const size_t header_len = strlen("MultisigV1");
 | 
			
		||||
  if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1")
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info header check error");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  std::string decoded;
 | 
			
		||||
  if (!tools::base58::decode(data.substr(header_len), decoded))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info decoding error");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info is corrupt");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t offset = 0;
 | 
			
		||||
  skey = *(const crypto::secret_key*)(decoded.data() + offset);
 | 
			
		||||
  offset += sizeof(skey);
 | 
			
		||||
  pkey = *(const crypto::public_key*)(decoded.data() + offset);
 | 
			
		||||
  offset += sizeof(pkey);
 | 
			
		||||
  const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset);
 | 
			
		||||
 | 
			
		||||
  crypto::hash hash;
 | 
			
		||||
  crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash);
 | 
			
		||||
  if (!crypto::check_signature(hash, pkey, signature))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info signature is invalid");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer)
 | 
			
		||||
{
 | 
			
		||||
  if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC)
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info header check error");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  std::string decoded;
 | 
			
		||||
  if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info decoding error");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info is corrupt");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info is corrupt");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key);
 | 
			
		||||
  size_t offset = 0;
 | 
			
		||||
  signer = *(const crypto::public_key*)(decoded.data() + offset);
 | 
			
		||||
  offset += sizeof(signer);
 | 
			
		||||
  const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key));
 | 
			
		||||
 | 
			
		||||
  crypto::hash hash;
 | 
			
		||||
  crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash);
 | 
			
		||||
  if (!crypto::check_signature(hash, signer, signature))
 | 
			
		||||
  {
 | 
			
		||||
    MERROR("Multisig info signature is invalid");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t n = 0; n < n_keys; ++n)
 | 
			
		||||
  {
 | 
			
		||||
    crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset);
 | 
			
		||||
    pkeys.insert(mspk);
 | 
			
		||||
    offset += sizeof(mspk);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
 | 
			
		||||
{
 | 
			
		||||
  if (!m_multisig)
 | 
			
		||||
| 
						 | 
				
			
			@ -5489,7 +5196,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const
 | 
			
		|||
    *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity()));
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
bool wallet2::has_multisig_partial_key_images() const
 | 
			
		||||
{
 | 
			
		||||
  if (!m_multisig)
 | 
			
		||||
| 
						 | 
				
			
			@ -5499,7 +5206,7 @@ bool wallet2::has_multisig_partial_key_images() const
 | 
			
		|||
      return true;
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
bool wallet2::has_unknown_key_images() const
 | 
			
		||||
{
 | 
			
		||||
  for (const auto &td: m_transfers)
 | 
			
		||||
| 
						 | 
				
			
			@ -13323,13 +13030,6 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
 | 
			
		|||
  return imported_outputs;
 | 
			
		||||
}
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
 | 
			
		||||
{
 | 
			
		||||
  crypto::public_key pkey;
 | 
			
		||||
  crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey);
 | 
			
		||||
  return pkey;
 | 
			
		||||
}
 | 
			
		||||
//----------------------------------------------------------------------------------------------------
 | 
			
		||||
crypto::public_key wallet2::get_multisig_signer_public_key() const
 | 
			
		||||
{
 | 
			
		||||
  CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig");
 | 
			
		||||
| 
						 | 
				
			
			@ -13373,7 +13073,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con
 | 
			
		|||
  CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index");
 | 
			
		||||
  rct::multisig_kLRki kLRki;
 | 
			
		||||
  kLRki.k = k;
 | 
			
		||||
  cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R);
 | 
			
		||||
  multisig::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R);
 | 
			
		||||
  kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image);
 | 
			
		||||
  return kLRki;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13420,7 +13120,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const
 | 
			
		|||
  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(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki);
 | 
			
		||||
  bool r = multisig::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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13443,7 +13143,7 @@ cryptonote::blobdata 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 = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki);
 | 
			
		||||
      bool r = multisig::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);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -757,45 +757,20 @@ private:
 | 
			
		|||
     * to other participants
 | 
			
		||||
     */
 | 
			
		||||
    std::string make_multisig(const epee::wipeable_string &password,
 | 
			
		||||
      const std::vector<std::string> &info,
 | 
			
		||||
      uint32_t threshold);
 | 
			
		||||
      const std::vector<std::string> &kex_messages,
 | 
			
		||||
      const std::uint32_t threshold);
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Creates a multisig wallet
 | 
			
		||||
     * \brief Increment the multisig key exchange round
 | 
			
		||||
     * \return empty if done, non empty if we need to send another string
 | 
			
		||||
     * to other participants
 | 
			
		||||
     */
 | 
			
		||||
    std::string make_multisig(const epee::wipeable_string &password,
 | 
			
		||||
      const std::vector<crypto::secret_key> &view_keys,
 | 
			
		||||
      const std::vector<crypto::public_key> &spend_keys,
 | 
			
		||||
      uint32_t threshold);
 | 
			
		||||
    std::string exchange_multisig_keys(const epee::wipeable_string &password,
 | 
			
		||||
      const std::vector<std::string> &info);
 | 
			
		||||
      const std::vector<std::string> &kex_messages);
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Any but first round of keys exchange
 | 
			
		||||
     * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called)
 | 
			
		||||
     * \return string to send to other participants
 | 
			
		||||
     */
 | 
			
		||||
    std::string exchange_multisig_keys(const epee::wipeable_string &password,
 | 
			
		||||
      std::unordered_set<crypto::public_key> pkeys,
 | 
			
		||||
      std::vector<crypto::public_key> signers);
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Finalizes creation of a multisig wallet
 | 
			
		||||
     */
 | 
			
		||||
    bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info);
 | 
			
		||||
    /*!
 | 
			
		||||
     * \brief Finalizes creation of a multisig wallet
 | 
			
		||||
     */
 | 
			
		||||
    bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers);
 | 
			
		||||
    /*!
 | 
			
		||||
     * Get a packaged multisig information string
 | 
			
		||||
     */
 | 
			
		||||
    std::string get_multisig_info() const;
 | 
			
		||||
    /*!
 | 
			
		||||
     * Verifies and extracts keys from a packaged multisig information string
 | 
			
		||||
     */
 | 
			
		||||
    static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey);
 | 
			
		||||
    /*!
 | 
			
		||||
     * Verifies and extracts keys from a packaged multisig information string
 | 
			
		||||
     */
 | 
			
		||||
    static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer);
 | 
			
		||||
    std::string get_multisig_first_kex_msg() const;
 | 
			
		||||
    /*!
 | 
			
		||||
     * Export multisig info
 | 
			
		||||
     * This will generate and remember new k values
 | 
			
		||||
| 
						 | 
				
			
			@ -1477,7 +1452,6 @@ private:
 | 
			
		|||
    void set_attribute(const std::string &key, const std::string &value);
 | 
			
		||||
    bool get_attribute(const std::string &key, std::string &value) const;
 | 
			
		||||
 | 
			
		||||
    crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const;
 | 
			
		||||
    crypto::public_key get_multisig_signer_public_key() const;
 | 
			
		||||
    crypto::public_key get_multisig_signing_public_key(size_t idx) const;
 | 
			
		||||
    crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -1641,12 +1615,6 @@ private:
 | 
			
		|||
    bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
 | 
			
		||||
 | 
			
		||||
    uint64_t get_segregation_fork_height() const;
 | 
			
		||||
    void unpack_multisig_info(const std::vector<std::string>& info,
 | 
			
		||||
      std::vector<crypto::public_key> &public_keys,
 | 
			
		||||
      std::vector<crypto::secret_key> &secret_keys) const;
 | 
			
		||||
    bool unpack_extra_multisig_info(const std::vector<std::string>& info,
 | 
			
		||||
      std::vector<crypto::public_key> &signers,
 | 
			
		||||
      std::unordered_set<crypto::public_key> &pkeys) const;
 | 
			
		||||
 | 
			
		||||
    void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const;
 | 
			
		||||
    std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3938,7 +3938,7 @@ namespace tools
 | 
			
		|||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.multisig_info = m_wallet->get_multisig_info();
 | 
			
		||||
    res.multisig_info = m_wallet->get_multisig_first_kex_msg();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  //------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -4069,7 +4069,7 @@ namespace tools
 | 
			
		|||
    catch (const std::exception &e)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
 | 
			
		||||
      er.message = "Error calling import_multisig";
 | 
			
		||||
      er.message = std::string{"Error calling import_multisig: "} + e.what();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4094,53 +4094,7 @@ namespace tools
 | 
			
		|||
  //------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx)
 | 
			
		||||
  {
 | 
			
		||||
    if (!m_wallet) return not_open(er);
 | 
			
		||||
    if (m_restricted)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_DENIED;
 | 
			
		||||
      er.message = "Command unavailable in restricted mode.";
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    bool ready;
 | 
			
		||||
    uint32_t threshold, total;
 | 
			
		||||
    if (!m_wallet->multisig(&ready, &threshold, &total))
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG;
 | 
			
		||||
      er.message = "This wallet is not multisig";
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (ready)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG;
 | 
			
		||||
      er.message = "This wallet is multisig, and already finalized";
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
 | 
			
		||||
      er.message = "Needs multisig info from more participants";
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try
 | 
			
		||||
    {
 | 
			
		||||
      if (!m_wallet->finalize_multisig(req.password, req.multisig_info))
 | 
			
		||||
      {
 | 
			
		||||
        er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
 | 
			
		||||
        er.message = "Error calling finalize_multisig";
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    catch (const std::exception &e)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
 | 
			
		||||
      er.message = std::string("Error calling finalize_multisig: ") + e.what();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  //------------------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
  bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
 | 
			
		||||
| 
						 | 
				
			
			@ -4168,7 +4122,7 @@ namespace tools
 | 
			
		|||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (req.multisig_info.size() < 1 || req.multisig_info.size() > total)
 | 
			
		||||
    if (req.multisig_info.size() + 1 < total)
 | 
			
		||||
    {
 | 
			
		||||
      er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED;
 | 
			
		||||
      er.message = "Needs multisig info from more participants";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,7 +47,7 @@
 | 
			
		|||
// advance which version they will stop working with
 | 
			
		||||
// Don't go over 32767 for any of these
 | 
			
		||||
#define WALLET_RPC_VERSION_MAJOR 1
 | 
			
		||||
#define WALLET_RPC_VERSION_MINOR 23
 | 
			
		||||
#define WALLET_RPC_VERSION_MINOR 24
 | 
			
		||||
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
 | 
			
		||||
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
 | 
			
		||||
namespace tools
 | 
			
		||||
| 
						 | 
				
			
			@ -2504,24 +2504,17 @@ namespace wallet_rpc
 | 
			
		|||
 | 
			
		||||
  struct COMMAND_RPC_FINALIZE_MULTISIG
 | 
			
		||||
  {
 | 
			
		||||
    // NOP
 | 
			
		||||
    struct request_t
 | 
			
		||||
    {
 | 
			
		||||
      std::string password;
 | 
			
		||||
      std::vector<std::string> multisig_info;
 | 
			
		||||
 | 
			
		||||
      BEGIN_KV_SERIALIZE_MAP()
 | 
			
		||||
        KV_SERIALIZE(password)
 | 
			
		||||
        KV_SERIALIZE(multisig_info)
 | 
			
		||||
      END_KV_SERIALIZE_MAP()
 | 
			
		||||
    };
 | 
			
		||||
    typedef epee::misc_utils::struct_init<request_t> request;
 | 
			
		||||
 | 
			
		||||
    struct response_t
 | 
			
		||||
    {
 | 
			
		||||
      std::string address;
 | 
			
		||||
 | 
			
		||||
      BEGIN_KV_SERIALIZE_MAP()
 | 
			
		||||
        KV_SERIALIZE(address)
 | 
			
		||||
      END_KV_SERIALIZE_MAP()
 | 
			
		||||
    };
 | 
			
		||||
    typedef epee::misc_utils::struct_init<response_t> response;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -836,7 +836,7 @@ inline bool do_replay_file(const std::string& filename)
 | 
			
		|||
    { \
 | 
			
		||||
      for (size_t msidx = 0; msidx < total; ++msidx) \
 | 
			
		||||
        account[msidx].generate(); \
 | 
			
		||||
      make_multisig_accounts(account, threshold); \
 | 
			
		||||
      CHECK_AND_ASSERT_MES(make_multisig_accounts(account, threshold), false, "Failed to make multisig accounts."); \
 | 
			
		||||
    } while(0)
 | 
			
		||||
 | 
			
		||||
#define MAKE_ACCOUNT(VEC_EVENTS, account) \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,98 +28,88 @@
 | 
			
		|||
// 
 | 
			
		||||
// 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 "common/apply_permutation.h"
 | 
			
		||||
#include "chaingen.h"
 | 
			
		||||
#include "multisig.h"
 | 
			
		||||
 | 
			
		||||
#include "common/apply_permutation.h"
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "cryptonote_basic/cryptonote_basic.h"
 | 
			
		||||
#include "device/device.hpp"
 | 
			
		||||
#include "multisig/multisig.h"
 | 
			
		||||
#include "multisig/multisig_account.h"
 | 
			
		||||
#include "multisig/multisig_kex_msg.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
#include "ringct/rctSigs.h"
 | 
			
		||||
 | 
			
		||||
using namespace epee;
 | 
			
		||||
using namespace crypto;
 | 
			
		||||
using namespace cryptonote;
 | 
			
		||||
using namespace multisig;
 | 
			
		||||
 | 
			
		||||
//#define NO_MULTISIG
 | 
			
		||||
 | 
			
		||||
void make_multisig_accounts(std::vector<cryptonote::account_base>& account, uint32_t threshold)
 | 
			
		||||
static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accounts, const uint32_t threshold)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<crypto::secret_key> all_view_keys;
 | 
			
		||||
  std::vector<std::vector<crypto::public_key>> derivations(account.size());
 | 
			
		||||
  //storage for all set of multisig derivations and spend public key (in first round)
 | 
			
		||||
  std::unordered_set<crypto::public_key> exchanging_keys;
 | 
			
		||||
  CHECK_AND_ASSERT_MES(accounts.size() > 0, false, "Invalid multisig scheme");
 | 
			
		||||
 | 
			
		||||
  for (size_t msidx = 0; msidx < account.size(); ++msidx)
 | 
			
		||||
  std::vector<multisig_account> multisig_accounts;
 | 
			
		||||
  std::vector<crypto::public_key> signers;
 | 
			
		||||
  std::vector<multisig_kex_msg> round_msgs;
 | 
			
		||||
  multisig_accounts.reserve(accounts.size());
 | 
			
		||||
  signers.reserve(accounts.size());
 | 
			
		||||
  round_msgs.reserve(accounts.size());
 | 
			
		||||
 | 
			
		||||
  // create multisig accounts
 | 
			
		||||
  for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
 | 
			
		||||
  {
 | 
			
		||||
    crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_view_secret_key);
 | 
			
		||||
    all_view_keys.push_back(vkh);
 | 
			
		||||
    // create account and collect signer
 | 
			
		||||
    multisig_accounts.emplace_back(
 | 
			
		||||
        multisig_account{
 | 
			
		||||
          get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_spend_secret_key),
 | 
			
		||||
          get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_view_secret_key)
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_spend_secret_key);
 | 
			
		||||
    crypto::public_key pskh;
 | 
			
		||||
    crypto::secret_key_to_public_key(skh, pskh);
 | 
			
		||||
    signers.emplace_back(multisig_accounts.back().get_base_pubkey());
 | 
			
		||||
 | 
			
		||||
    derivations[msidx].push_back(pskh);
 | 
			
		||||
    exchanging_keys.insert(pskh);
 | 
			
		||||
    // collect account's first kex msg
 | 
			
		||||
    round_msgs.emplace_back(multisig_accounts.back().get_next_kex_round_msg());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t roundsTotal = 1;
 | 
			
		||||
  if (threshold < account.size())
 | 
			
		||||
    roundsTotal = account.size() - threshold;
 | 
			
		||||
 | 
			
		||||
  //secret multisig keys of every account
 | 
			
		||||
  std::vector<std::vector<crypto::secret_key>> multisig_keys(account.size());
 | 
			
		||||
  std::vector<crypto::secret_key> spend_skey(account.size());
 | 
			
		||||
  std::vector<crypto::public_key> spend_pkey(account.size());
 | 
			
		||||
  for (uint32_t round = 0; round < roundsTotal; ++round)
 | 
			
		||||
  // initialize accounts and collect kex messages for the next round
 | 
			
		||||
  std::vector<multisig_kex_msg> temp_round_msgs(multisig_accounts.size());
 | 
			
		||||
  for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
 | 
			
		||||
  {
 | 
			
		||||
    std::unordered_set<crypto::public_key> roundKeys;
 | 
			
		||||
    for (size_t msidx = 0; msidx < account.size(); ++msidx)
 | 
			
		||||
    multisig_accounts[account_index].initialize_kex(threshold, signers, round_msgs);
 | 
			
		||||
 | 
			
		||||
    if (!multisig_accounts[account_index].multisig_is_ready())
 | 
			
		||||
      temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // perform key exchange rounds
 | 
			
		||||
  while (!multisig_accounts[0].multisig_is_ready())
 | 
			
		||||
  {
 | 
			
		||||
    round_msgs = temp_round_msgs;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t account_index{0}; account_index < multisig_accounts.size(); ++account_index)
 | 
			
		||||
    {
 | 
			
		||||
      // subtracting one's keys from set of all unique keys is the same as key exchange
 | 
			
		||||
      auto myKeys = exchanging_keys;
 | 
			
		||||
      for (const auto& d: derivations[msidx])
 | 
			
		||||
          myKeys.erase(d);
 | 
			
		||||
      multisig_accounts[account_index].kex_update(round_msgs);
 | 
			
		||||
 | 
			
		||||
      if (threshold == account.size())
 | 
			
		||||
      {
 | 
			
		||||
        cryptonote::generate_multisig_N_N(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end()), multisig_keys[msidx], (rct::key&)spend_skey[msidx], (rct::key&)spend_pkey[msidx]);
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        derivations[msidx] = cryptonote::generate_multisig_derivations(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end()));
 | 
			
		||||
        roundKeys.insert(derivations[msidx].begin(), derivations[msidx].end());
 | 
			
		||||
      }
 | 
			
		||||
      if (!multisig_accounts[account_index].multisig_is_ready())
 | 
			
		||||
        temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exchanging_keys = roundKeys;
 | 
			
		||||
    roundKeys.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::unordered_set<crypto::public_key> all_multisig_keys;
 | 
			
		||||
  for (size_t msidx = 0; msidx < account.size(); ++msidx)
 | 
			
		||||
  // update accounts post key exchange
 | 
			
		||||
  for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index)
 | 
			
		||||
  {
 | 
			
		||||
    std::unordered_set<crypto::secret_key> view_keys(all_view_keys.begin(), all_view_keys.end());
 | 
			
		||||
    view_keys.erase(all_view_keys[msidx]);
 | 
			
		||||
 | 
			
		||||
    crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, std::vector<secret_key>(view_keys.begin(), view_keys.end()));
 | 
			
		||||
    if (threshold < account.size())
 | 
			
		||||
    {
 | 
			
		||||
      multisig_keys[msidx] = cryptonote::calculate_multisig_keys(derivations[msidx]);
 | 
			
		||||
      spend_skey[msidx] = cryptonote::calculate_multisig_signer_key(multisig_keys[msidx]);
 | 
			
		||||
    }
 | 
			
		||||
    account[msidx].make_multisig(view_skey, spend_skey[msidx], spend_pkey[msidx], multisig_keys[msidx]);
 | 
			
		||||
    for (const auto &k: multisig_keys[msidx]) {
 | 
			
		||||
      all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k))));
 | 
			
		||||
    }
 | 
			
		||||
    accounts[account_index].make_multisig(multisig_accounts[account_index].get_common_privkey(),
 | 
			
		||||
      multisig_accounts[account_index].get_base_privkey(),
 | 
			
		||||
      multisig_accounts[account_index].get_multisig_pubkey(),
 | 
			
		||||
      multisig_accounts[account_index].get_multisig_privkeys());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (threshold < account.size())
 | 
			
		||||
  {
 | 
			
		||||
    std::vector<crypto::public_key> public_keys(std::vector<crypto::public_key>(all_multisig_keys.begin(), all_multisig_keys.end()));
 | 
			
		||||
    crypto::public_key spend_pkey = cryptonote::generate_multisig_M_N_spend_public_key(public_keys);
 | 
			
		||||
 | 
			
		||||
    for (size_t msidx = 0; msidx < account.size(); ++msidx)
 | 
			
		||||
      account[msidx].finalize_multisig(spend_pkey);
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------------------------------------------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -238,13 +228,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 | 
			
		|||
      for (size_t n = 0; n < nlr; ++n)
 | 
			
		||||
      {
 | 
			
		||||
        account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen()));
 | 
			
		||||
        cryptonote::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]);
 | 
			
		||||
        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)
 | 
			
		||||
      {
 | 
			
		||||
        r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][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");
 | 
			
		||||
      }
 | 
			
		||||
      MDEBUG("Party " << msidx << ":");
 | 
			
		||||
| 
						 | 
				
			
			@ -303,7 +293,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 | 
			
		|||
    for (size_t msidx = 0; msidx < total; ++msidx)
 | 
			
		||||
      for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n)
 | 
			
		||||
        pkis.push_back(account_ki[msidx][tdidx][n]);
 | 
			
		||||
    r = cryptonote::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);
 | 
			
		||||
    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);
 | 
			
		||||
| 
						 | 
				
			
			@ -311,7 +301,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 | 
			
		|||
    for (size_t n = 1; n < total; ++n)
 | 
			
		||||
    {
 | 
			
		||||
      rct::key ki;
 | 
			
		||||
      r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki);
 | 
			
		||||
      r = multisig::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki);
 | 
			
		||||
      CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image");
 | 
			
		||||
      CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match");
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,40 +39,40 @@ from framework.wallet import Wallet
 | 
			
		|||
class MultisigTest():
 | 
			
		||||
    def run_test(self):
 | 
			
		||||
        self.reset()
 | 
			
		||||
        self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5)
 | 
			
		||||
        self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5)
 | 
			
		||||
        self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5)
 | 
			
		||||
        self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5)
 | 
			
		||||
        self.mine('4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW', 5)
 | 
			
		||||
        self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5)
 | 
			
		||||
        self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5)
 | 
			
		||||
        self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5)
 | 
			
		||||
        self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5)
 | 
			
		||||
        self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5)
 | 
			
		||||
        self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60)
 | 
			
		||||
 | 
			
		||||
        self.test_states()
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk')
 | 
			
		||||
        self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG')
 | 
			
		||||
        self.import_multisig_info([1, 0], 5)
 | 
			
		||||
        txid = self.transfer([1, 0])
 | 
			
		||||
        self.import_multisig_info([0, 1], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y')
 | 
			
		||||
        self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i')
 | 
			
		||||
        self.import_multisig_info([0, 2], 5)
 | 
			
		||||
        txid = self.transfer([0, 2])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(3, 3, '4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW')
 | 
			
		||||
        self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP')
 | 
			
		||||
        self.import_multisig_info([2, 0, 1], 5)
 | 
			
		||||
        txid = self.transfer([2, 1, 0])
 | 
			
		||||
        self.import_multisig_info([0, 2, 1], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53')
 | 
			
		||||
        self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff')
 | 
			
		||||
        self.import_multisig_info([0, 2, 3], 5)
 | 
			
		||||
        txid = self.transfer([0, 2, 3])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR')
 | 
			
		||||
        self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U')
 | 
			
		||||
        self.import_multisig_info([1, 2], 5)
 | 
			
		||||
        txid = self.transfer([1, 2])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 6)
 | 
			
		||||
| 
						 | 
				
			
			@ -176,10 +176,6 @@ class MultisigTest():
 | 
			
		|||
            info.append(res.multisig_info)
 | 
			
		||||
 | 
			
		||||
        for i in range(3):
 | 
			
		||||
            ok = False
 | 
			
		||||
            try: res = wallet[i].finalize_multisig(info)
 | 
			
		||||
            except: ok = True
 | 
			
		||||
            assert ok
 | 
			
		||||
            ok = False
 | 
			
		||||
            try: res = wallet[i].exchange_multisig_keys(info)
 | 
			
		||||
            except: ok = True
 | 
			
		||||
| 
						 | 
				
			
			@ -192,11 +188,6 @@ class MultisigTest():
 | 
			
		|||
        assert res.multisig
 | 
			
		||||
        assert res.ready
 | 
			
		||||
 | 
			
		||||
        ok = False
 | 
			
		||||
        try: res = wallet[0].finalize_multisig(info)
 | 
			
		||||
        except: ok = True
 | 
			
		||||
        assert ok
 | 
			
		||||
 | 
			
		||||
        ok = False
 | 
			
		||||
        try: res = wallet[0].prepare_multisig()
 | 
			
		||||
        except: ok = True
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,12 +26,16 @@
 | 
			
		|||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 | 
			
		||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
#include "crypto/crypto.h"
 | 
			
		||||
#include "multisig/multisig_account.h"
 | 
			
		||||
#include "multisig/multisig_kex_msg.h"
 | 
			
		||||
#include "ringct/rctOps.h"
 | 
			
		||||
#include "wallet/wallet2.h"
 | 
			
		||||
 | 
			
		||||
#include "gtest/gtest.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
 | 
			
		||||
#include "wallet/wallet2.h"
 | 
			
		||||
 | 
			
		||||
static const struct
 | 
			
		||||
{
 | 
			
		||||
  const char *address;
 | 
			
		||||
| 
						 | 
				
			
			@ -86,59 +90,145 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet)
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& mis)
 | 
			
		||||
static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& infos)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<std::string> new_infos;
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i) {
 | 
			
		||||
      new_infos.push_back(wallets[i].exchange_multisig_keys("", mis));
 | 
			
		||||
  new_infos.reserve(infos.size());
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
      new_infos.push_back(wallets[i].exchange_multisig_keys("", infos));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new_infos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void check_results(const std::vector<std::string> &intermediate_infos,
 | 
			
		||||
  std::vector<tools::wallet2>& wallets,
 | 
			
		||||
  std::uint32_t M)
 | 
			
		||||
{
 | 
			
		||||
  // check results
 | 
			
		||||
  std::unordered_set<crypto::secret_key> unique_privkeys;
 | 
			
		||||
  rct::key composite_pubkey = rct::identity();
 | 
			
		||||
 | 
			
		||||
  wallets[0].decrypt_keys("");
 | 
			
		||||
  crypto::public_key spend_pubkey = wallets[0].get_account().get_keys().m_account_address.m_spend_public_key;
 | 
			
		||||
  crypto::secret_key view_privkey = wallets[0].get_account().get_keys().m_view_secret_key;
 | 
			
		||||
  crypto::public_key view_pubkey;
 | 
			
		||||
  EXPECT_TRUE(crypto::secret_key_to_public_key(view_privkey, view_pubkey));
 | 
			
		||||
  wallets[0].encrypt_keys("");
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    EXPECT_TRUE(intermediate_infos[i].empty());
 | 
			
		||||
    bool ready;
 | 
			
		||||
    uint32_t threshold, total;
 | 
			
		||||
    EXPECT_TRUE(wallets[i].multisig(&ready, &threshold, &total));
 | 
			
		||||
    EXPECT_TRUE(ready);
 | 
			
		||||
    EXPECT_TRUE(threshold == M);
 | 
			
		||||
    EXPECT_TRUE(total == wallets.size());
 | 
			
		||||
 | 
			
		||||
    wallets[i].decrypt_keys("");
 | 
			
		||||
 | 
			
		||||
    if (i != 0)
 | 
			
		||||
    {
 | 
			
		||||
      // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses.
 | 
			
		||||
      // no need to compare 0's address with itself.
 | 
			
		||||
      EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) ==
 | 
			
		||||
        wallets[i].get_account().get_public_address_str(cryptonote::TESTNET));
 | 
			
		||||
      
 | 
			
		||||
      EXPECT_EQ(spend_pubkey, wallets[i].get_account().get_keys().m_account_address.m_spend_public_key);
 | 
			
		||||
      EXPECT_EQ(view_privkey, wallets[i].get_account().get_keys().m_view_secret_key);
 | 
			
		||||
      EXPECT_EQ(view_pubkey, wallets[i].get_account().get_keys().m_account_address.m_view_public_key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // sum together unique multisig keys
 | 
			
		||||
    for (const auto &privkey : wallets[i].get_account().get_keys().m_multisig_keys)
 | 
			
		||||
    {
 | 
			
		||||
      EXPECT_NE(privkey, crypto::null_skey);
 | 
			
		||||
 | 
			
		||||
      if (unique_privkeys.find(privkey) == unique_privkeys.end())
 | 
			
		||||
      {
 | 
			
		||||
        unique_privkeys.insert(privkey);
 | 
			
		||||
        crypto::public_key pubkey;
 | 
			
		||||
        crypto::secret_key_to_public_key(privkey, pubkey);
 | 
			
		||||
        EXPECT_NE(privkey, crypto::null_skey);
 | 
			
		||||
        EXPECT_NE(pubkey, crypto::null_pkey);
 | 
			
		||||
        EXPECT_NE(pubkey, rct::rct2pk(rct::identity()));
 | 
			
		||||
        rct::addKeys(composite_pubkey, composite_pubkey, rct::pk2rct(pubkey));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    wallets[i].encrypt_keys("");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // final key via sums should equal the wallets' public spend key
 | 
			
		||||
  wallets[0].decrypt_keys("");
 | 
			
		||||
  EXPECT_EQ(wallets[0].get_account().get_keys().m_account_address.m_spend_public_key, rct::rct2pk(composite_pubkey));
 | 
			
		||||
  wallets[0].encrypt_keys("");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M)
 | 
			
		||||
{
 | 
			
		||||
  ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT);
 | 
			
		||||
  ASSERT_TRUE(M <= wallets.size());
 | 
			
		||||
  std::uint32_t rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M);
 | 
			
		||||
  std::uint32_t rounds_complete{0};
 | 
			
		||||
 | 
			
		||||
  std::vector<std::string> mis(wallets.size());
 | 
			
		||||
  // initialize wallets, get first round multisig kex msgs
 | 
			
		||||
  std::vector<std::string> initial_infos(wallets.size());
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i) {
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    make_wallet(i, wallets[i]);
 | 
			
		||||
 | 
			
		||||
    wallets[i].decrypt_keys("");
 | 
			
		||||
    mis[i] = wallets[i].get_multisig_info();
 | 
			
		||||
    initial_infos[i] = wallets[i].get_multisig_first_kex_msg();
 | 
			
		||||
    wallets[i].encrypt_keys("");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto& wallet: wallets) {
 | 
			
		||||
  // wallets should not be multisig yet
 | 
			
		||||
  for (const auto &wallet: wallets)
 | 
			
		||||
  {
 | 
			
		||||
    ASSERT_FALSE(wallet.multisig());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<std::string> mxis;
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i) {
 | 
			
		||||
    // it's ok to put all of multisig keys in this function. it throws in case of error
 | 
			
		||||
    mxis.push_back(wallets[i].make_multisig("", mis, M));
 | 
			
		||||
  // make wallets multisig, get second round kex messages (if appropriate)
 | 
			
		||||
  std::vector<std::string> intermediate_infos(wallets.size());
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i)
 | 
			
		||||
  {
 | 
			
		||||
    intermediate_infos[i] = wallets[i].make_multisig("", initial_infos, M);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while (!mxis[0].empty()) {
 | 
			
		||||
    mxis = exchange_round(wallets, mxis);
 | 
			
		||||
  ++rounds_complete;
 | 
			
		||||
 | 
			
		||||
  // perform kex rounds until kex is complete
 | 
			
		||||
  while (!intermediate_infos[0].empty())
 | 
			
		||||
  {
 | 
			
		||||
    bool ready{false};
 | 
			
		||||
    wallets[0].multisig(&ready);
 | 
			
		||||
    EXPECT_FALSE(ready);
 | 
			
		||||
 | 
			
		||||
    intermediate_infos = exchange_round(wallets, intermediate_infos);
 | 
			
		||||
 | 
			
		||||
    ++rounds_complete;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < wallets.size(); ++i) {
 | 
			
		||||
    ASSERT_TRUE(mxis[i].empty());
 | 
			
		||||
    bool ready;
 | 
			
		||||
    uint32_t threshold, total;
 | 
			
		||||
    ASSERT_TRUE(wallets[i].multisig(&ready, &threshold, &total));
 | 
			
		||||
    ASSERT_TRUE(ready);
 | 
			
		||||
    ASSERT_TRUE(threshold == M);
 | 
			
		||||
    ASSERT_TRUE(total == wallets.size());
 | 
			
		||||
  EXPECT_EQ(rounds_required, rounds_complete);
 | 
			
		||||
 | 
			
		||||
    if (i != 0) {
 | 
			
		||||
      // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses. no need to compare 0's address with itself.
 | 
			
		||||
      ASSERT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == wallets[i].get_account().get_public_address_str(cryptonote::TESTNET));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  check_results(intermediate_infos, wallets, M);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(multisig, make_1_2)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<tools::wallet2> wallets(2);
 | 
			
		||||
  make_wallets(wallets, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(multisig, make_1_3)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<tools::wallet2> wallets(3);
 | 
			
		||||
  make_wallets(wallets, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(multisig, make_2_2)
 | 
			
		||||
| 
						 | 
				
			
			@ -165,8 +255,88 @@ TEST(multisig, make_2_4)
 | 
			
		|||
  make_wallets(wallets, 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST(multisig, make_2_5)
 | 
			
		||||
TEST(multisig, multisig_kex_msg)
 | 
			
		||||
{
 | 
			
		||||
  std::vector<tools::wallet2> wallets(5);
 | 
			
		||||
  make_wallets(wallets, 2);
 | 
			
		||||
  using namespace multisig;
 | 
			
		||||
 | 
			
		||||
  crypto::public_key pubkey1;
 | 
			
		||||
  crypto::public_key pubkey2;
 | 
			
		||||
  crypto::public_key pubkey3;
 | 
			
		||||
  crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey1);
 | 
			
		||||
  crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey2);
 | 
			
		||||
  crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey3);
 | 
			
		||||
 | 
			
		||||
  crypto::secret_key signing_skey = rct::rct2sk(rct::skGen());
 | 
			
		||||
  crypto::public_key signing_pubkey;
 | 
			
		||||
  while(!crypto::secret_key_to_public_key(signing_skey, signing_pubkey))
 | 
			
		||||
  {
 | 
			
		||||
    signing_skey = rct::rct2sk(rct::skGen());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  crypto::secret_key ancillary_skey = rct::rct2sk(rct::skGen());
 | 
			
		||||
  while (ancillary_skey == crypto::null_skey)
 | 
			
		||||
    ancillary_skey = rct::rct2sk(rct::skGen());
 | 
			
		||||
 | 
			
		||||
  // misc. edge cases
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{multisig_kex_msg{}.get_msg()}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{"abc"}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{0, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, crypto::null_skey}));
 | 
			
		||||
  EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, ancillary_skey}));
 | 
			
		||||
 | 
			
		||||
  // test that messages are both constructible and reversible
 | 
			
		||||
 | 
			
		||||
  // round 1
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, ancillary_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
  // round 2
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, crypto::null_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
  EXPECT_NO_THROW((multisig_kex_msg{
 | 
			
		||||
      multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2, pubkey3}, crypto::null_skey}.get_msg()
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
  // test that keys can be recovered if stored in a message and the message's reverse
 | 
			
		||||
 | 
			
		||||
  // round 1
 | 
			
		||||
  multisig_kex_msg msg_rnd1{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey};
 | 
			
		||||
  multisig_kex_msg msg_rnd1_reverse{msg_rnd1.get_msg()};
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_round(), 1);
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_round(), msg_rnd1_reverse.get_round());
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_signing_pubkey(), signing_pubkey);
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_signing_pubkey(), msg_rnd1_reverse.get_signing_pubkey());
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), 0);
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), msg_rnd1_reverse.get_msg_pubkeys().size());
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_msg_privkey(), ancillary_skey);
 | 
			
		||||
  EXPECT_EQ(msg_rnd1.get_msg_privkey(), msg_rnd1_reverse.get_msg_privkey());
 | 
			
		||||
 | 
			
		||||
  // round 2
 | 
			
		||||
  multisig_kex_msg msg_rnd2{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey};
 | 
			
		||||
  multisig_kex_msg msg_rnd2_reverse{msg_rnd2.get_msg()};
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_round(), 2);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_round(), msg_rnd2_reverse.get_round());
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_signing_pubkey(), signing_pubkey);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_signing_pubkey(), msg_rnd2_reverse.get_signing_pubkey());
 | 
			
		||||
  ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), 2);
 | 
			
		||||
  ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), msg_rnd2_reverse.get_msg_pubkeys().size());
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], pubkey1);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], pubkey2);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], msg_rnd2_reverse.get_msg_pubkeys()[0]);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], msg_rnd2_reverse.get_msg_pubkeys()[1]);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_privkey(), crypto::null_skey);
 | 
			
		||||
  EXPECT_EQ(msg_rnd2.get_msg_privkey(), msg_rnd2_reverse.get_msg_privkey());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -512,14 +512,12 @@ class Wallet(object):
 | 
			
		|||
        }
 | 
			
		||||
        return self.rpc.send_json_rpc_request(make_multisig)
 | 
			
		||||
 | 
			
		||||
    def finalize_multisig(self, multisig_info, password = ''):
 | 
			
		||||
    def finalize_multisig(self):
 | 
			
		||||
        finalize_multisig = {
 | 
			
		||||
            'method': 'finalize_multisig',
 | 
			
		||||
            'params' : {
 | 
			
		||||
                'multisig_info': multisig_info,
 | 
			
		||||
                'password': password,
 | 
			
		||||
            },
 | 
			
		||||
            'jsonrpc': '2.0',
 | 
			
		||||
            'jsonrpc': '2.0', 
 | 
			
		||||
            'id': '0'
 | 
			
		||||
        }
 | 
			
		||||
        return self.rpc.send_json_rpc_request(finalize_multisig)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue