Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet-rpc: restore from multisig seed (and fix generation) #8914

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions contrib/epee/include/span.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ namespace epee
return {reinterpret_cast<const std::uint8_t*>(src.data()), src.size_bytes()};
}

//! \return `span<std::uint8_t>` from a STL compatible `src`.
template<typename T>
constexpr span<std::uint8_t> to_mut_byte_span(T& src)
{
using value_type = typename T::value_type;
static_assert(!std::is_empty<value_type>(), "empty value types will not work -> sizeof == 1");
static_assert(!has_padding<value_type>(), "source value type may have padding");
return {reinterpret_cast<std::uint8_t*>(src.data()), src.size() * sizeof(value_type)};
}

//! \return `span<const std::uint8_t>` which represents the bytes at `&src`.
template<typename T>
span<const std::uint8_t> as_byte_span(const T& src) noexcept
Expand Down
47 changes: 26 additions & 21 deletions src/wallet/wallet2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,21 @@ bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry,
return false;
}

// Given M (threshold) and N (total), calculate the number of private multisig keys each
// signer should have. This value is equal to (N - 1) choose (N - M)
// Prereq: M >= 1 && N >= M && N <= 16
uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total)
{
THROW_WALLET_EXCEPTION_IF(threshold < 1 || total < threshold || threshold > 16,
tools::error::wallet_internal_error, "Invalid arguments to num_priv_multisig_keys_post_setup");

uint64_t n_multisig_keys = 1;
for (uint64_t i = 2; i <= total - 1; ++i) n_multisig_keys *= i; // multiply by (N - 1)!
for (uint64_t i = 2; i <= total - threshold; ++i) n_multisig_keys /= i; // divide by (N - M)!
for (uint64_t i = 2; i <= threshold - 1; ++i) n_multisig_keys /= i; // divide by ((N - 1) - (N - M))!
return n_multisig_keys;
}
Comment on lines +989 to +1002
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UkoeHB This formula appears to work correctly to calculate the number of private multisig keys per signer, thanks!


//-----------------------------------------------------------------
} //namespace

Expand Down Expand Up @@ -1394,7 +1409,7 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase, bool raw) const
bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const
{
bool ready;
uint32_t threshold, total;
Expand All @@ -1408,15 +1423,14 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
std::cout << "This multisig wallet is not yet finalized" << std::endl;
return false;
}
if (!raw && seed_language.empty())
{
std::cout << "seed_language not set" << std::endl;
return false;
}

const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(threshold, total);

crypto::secret_key skey;
crypto::public_key pkey;
const account_keys &keys = get_account().get_keys();
THROW_WALLET_EXCEPTION_IF(num_expected_ms_keys != keys.m_multisig_keys.size(),
error::wallet_internal_error, "Unexpected number of private multisig keys")
epee::wipeable_string data;
data.append((const char*)&threshold, sizeof(uint32_t));
data.append((const char*)&total, sizeof(uint32_t));
Expand All @@ -1441,18 +1455,7 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
data = encrypt(data, key, true);
}

if (raw)
{
seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()});
}
else
{
if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language))
{
std::cout << "Failed to encode seed";
return false;
}
}
seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()});

return true;
}
Expand Down Expand Up @@ -4905,9 +4908,11 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
offset += sizeof(uint32_t);
uint32_t total = *(uint32_t*)(multisig_data.data() + offset);
offset += sizeof(uint32_t);
THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed);
THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed);
const size_t n_multisig_keys = total == threshold ? 1 : threshold;

THROW_WALLET_EXCEPTION_IF(threshold < 1, error::invalid_multisig_seed);
THROW_WALLET_EXCEPTION_IF(total < threshold, error::invalid_multisig_seed);
THROW_WALLET_EXCEPTION_IF(threshold > 16, error::invalid_multisig_seed); // doing N choose (N - M + 1) might overflow
const uint64_t n_multisig_keys = num_priv_multisig_keys_post_setup(threshold, total);
THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed);

std::vector<crypto::secret_key> multisig_keys;
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet2.h
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ namespace tools
bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
bool has_multisig_partial_key_images() const;
bool has_unknown_key_images() const;
bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const;
bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string()) const;
bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; }
hw::device::device_type get_device_type() const { return m_key_device_type; }
bool reconnect_device();
Expand Down
33 changes: 30 additions & 3 deletions src/wallet/wallet_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3807,7 +3807,7 @@ namespace tools
std::string old_language;

// check the given seed
{
if (!req.enable_multisig_experimental) {
jeffro256 marked this conversation as resolved.
Show resolved Hide resolved
if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, old_language))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
Expand All @@ -3830,6 +3830,13 @@ namespace tools

// process seed_offset if given
{
if (req.enable_multisig_experimental && !req.seed_offset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Multisig seeds are not compatible with seed offsets";
return false;
}

if (!req.seed_offset.empty())
{
recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset);
Expand Down Expand Up @@ -3893,7 +3900,27 @@ namespace tools
crypto::secret_key recovery_val;
try
{
recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false);
if (req.enable_multisig_experimental)
{
// Parse multisig seed into raw multisig data
epee::wipeable_string multisig_data;
multisig_data.resize(req.seed.size() / 2);
jeffro256 marked this conversation as resolved.
Show resolved Hide resolved
if (!epee::from_hex::to_buffer(epee::to_mut_byte_span(multisig_data), req.seed))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Multisig seed not represented as hexadecimal string";
return false;
}

// Generate multisig wallet
wal->generate(wallet_file, std::move(rc.second).password(), multisig_data, false);
jeffro256 marked this conversation as resolved.
Show resolved Hide resolved
wal->enable_multisig(true);
}
else
{
// Generate normal wallet
recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false);
}
MINFO("Wallet has been restored.\n");
}
catch (const std::exception &e)
Expand All @@ -3904,7 +3931,7 @@ namespace tools

// // Convert the secret key back to seed
epee::wipeable_string electrum_words;
if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language))
if (!req.enable_multisig_experimental && !crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to encode seed";
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/wallet_rpc_server_commands_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,7 @@ namespace wallet_rpc
std::string password;
std::string language;
bool autosave_current;
bool enable_multisig_experimental;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_OPT(restore_height, (uint64_t)0)
Expand All @@ -2369,6 +2370,7 @@ namespace wallet_rpc
KV_SERIALIZE(password)
KV_SERIALIZE(language)
KV_SERIALIZE_OPT(autosave_current, true)
KV_SERIALIZE_OPT(enable_multisig_experimental, false)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
Expand Down
Loading