Skip to content

Commit

Permalink
Improve wallet seed handling and add another deterministic generation
Browse files Browse the repository at this point in the history
improved generalization

m_seed added to account properties
- allows seed to not be the stored spend key if desired.

This allows wallet to work with MyMonero.com.

TODO:

checksum on 12 words (use improved checksum generation and checking code
I already wrote on an older branch)

runtime selection of which deterministic mode to use
- possibly also store as a property of account like m_seed
  • Loading branch information
warptangent committed Mar 16, 2015
1 parent f6cbfb6 commit 35b1500
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 22 deletions.
85 changes: 81 additions & 4 deletions src/cryptonote_core/account.cpp
Expand Up @@ -44,6 +44,9 @@ extern "C"
#include "cryptonote_core/cryptonote_format_utils.h"
using namespace std;

// TODO CONFIG
int mode_key_generation = 1; // MyMonero

DISABLE_VS_WARNINGS(4244 4345)

namespace cryptonote
Expand All @@ -59,13 +62,87 @@ DISABLE_VS_WARNINGS(4244 4345)
m_keys = account_keys();
}
//-----------------------------------------------------------------
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random)
crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random, size_t num_words)
{
crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover);
crypto::secret_key use_recovery_key;
if (recover)
{
bool half_seed = false;
// for debugging only
if (num_words == 12)
{
half_seed = true;
}
// TODO: finish implementation and test
// this is also for when recovery from seed hex is supported.
size_t len = sizeof(recovery_key.data);
while ((len > 0) && (! recovery_key.data[len - 1])) {
--len;
}
if (len <= sizeof(crypto::secret_key) / 2) // TODO refactor with elsewhere
{
std::cout << "key length minus end null bytes less than normal size/2" << std::endl;
if ((num_words) && (num_words != 12))
{
throw std::runtime_error("Seed words used, but the number doesn't match expected number for a recovery key half filled.");
}
half_seed = true;
}
else if (num_words == 12)
{
throw std::runtime_error("Seed words used, with half the normal number, but the recovery key doesn't look half filled.");
}
std::cout << "num_words: " << num_words << " half_seed? " << half_seed << std::endl;
if (! half_seed)
{
use_recovery_key = recovery_key;
}
else
{
if (mode_key_generation == 0)
{
use_recovery_key = recovery_key;
memcpy(use_recovery_key.data + 16, use_recovery_key.data, 16); // if electrum 12-word seed, duplicate
}
else if (mode_key_generation == 1)
{
keccak((uint8_t *)&recovery_key.data, sizeof(crypto::secret_key) / 2, (uint8_t *)&use_recovery_key.data, sizeof(crypto::secret_key));
}
else
{
// TODO: throw runtime exception
}
std::cout << "use_recovery_key.data: " << epee::string_tools::pod_to_hex(use_recovery_key.data) << std::endl;
// because of keccak used to expand can't reverse this back to the twelve-word seed, only to new twenty-four words.
}

// recovery key deemed acceptable, assign to stored seed
m_keys.m_seed = recovery_key;
}

crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, use_recovery_key, recover);

if (! recover)
{
m_keys.m_seed = first;
}

// rng for generating second set of keys is hash of first rng. means only one set of electrum-style words needed for recovery
crypto::secret_key second;
keccak((uint8_t *)&m_keys.m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));

if (mode_key_generation == 0)
{
// monero behavior
keccak((uint8_t *)&m_keys.m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));
}
else if (mode_key_generation == 1)
{
// mymonero behavior
// "first" is the rng / provided seed recovery key prior to sc_reduce32().
// note that in original behavior, sc_reduce and a halving is done during
// rng generation. see random_scalar().
keccak((uint8_t *)&first, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));
}

generate_keys(m_keys.m_account_address.m_view_public_key, m_keys.m_view_secret_key, second, two_random ? false : true);

Expand All @@ -85,7 +162,7 @@ DISABLE_VS_WARNINGS(4244 4345)
{
m_creation_timestamp = time(NULL);
}
return first;
return m_keys.m_seed;
}
//-----------------------------------------------------------------
const account_keys& account_base::get_keys() const
Expand Down
4 changes: 3 additions & 1 deletion src/cryptonote_core/account.h
Expand Up @@ -42,11 +42,13 @@ namespace cryptonote
account_public_address m_account_address;
crypto::secret_key m_spend_secret_key;
crypto::secret_key m_view_secret_key;
crypto::secret_key m_seed;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(m_account_address)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_seed)
END_KV_SERIALIZE_MAP()
};

Expand All @@ -57,7 +59,7 @@ namespace cryptonote
{
public:
account_base();
crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false);
crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false, size_t num_words = 0);
const account_keys& get_keys() const;
std::string get_public_address_str(bool testnet);

Expand Down
35 changes: 32 additions & 3 deletions src/mnemonics/electrum-words.cpp
Expand Up @@ -200,13 +200,14 @@ namespace crypto
* \return false if not a multiple of 3 words, or if word is not in the words list
*/
bool words_to_bytes(std::string words, crypto::secret_key& dst,
std::string &language_name)
std::string &language_name, size_t &num_words)
{
std::vector<std::string> seed;

boost::algorithm::trim(words);
boost::split(seed, words, boost::is_any_of(" "));

num_words = seed.size(); // TODO replace seed.size() below with num_words
// error on non-compliant word list
if (seed.size() != seed_length/2 && seed.size() != seed_length &&
seed.size() != seed_length + 1)
Expand Down Expand Up @@ -236,6 +237,7 @@ namespace crypto
seed.pop_back();
}

memset(dst.data, 0, sizeof(dst.data));
for (unsigned int i=0; i < seed.size() / 3; i++)
{
uint32_t val;
Expand All @@ -255,11 +257,26 @@ namespace crypto
std::string wlist_copy = words;
if (seed.size() == seed_length/2)
{
memcpy(dst.data+16, dst.data, 16); // if electrum 12-word seed, duplicate
// for when 12 words are used:
// use dst.data half-filled as seed, so seed words can be re-obtained later.
// (due to keccak expansion below, we can't do that otherwise.
// so expand later when seed is to be used, but store this half-filled version as the actual seed.

// mode_key_generation: 0
// memcpy(dst.data+16, dst.data, 16); // if electrum 12-word seed, duplicate
//
// mode_key_generation: 1 MyMonero.com
// keccak((uint8_t *)&dst.data, sizeof(crypto::secret_key) / 2, (uint8_t *)&dst.data, sizeof(crypto::secret_key));
// because of keccak used to expand can't reverse this back to the twelve-word seed, only twenty-four, we handle the use of the seed later.

// TODO: this is from the past and was used to verify the words can be regenerated from the seed.
// since we're using the correct notion of "seed" now for the 12 words too (handling seed expansion later), this check can be added back.
wlist_copy += ' ';
wlist_copy += words;
}

// TODO: add seed verification check here.

return true;
}

Expand Down Expand Up @@ -302,8 +319,20 @@ namespace crypto
std::vector<std::string> words_store;

uint32_t word_list_length = word_list.size();
// for fully used src.data:
// 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626
for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ')
//
// half that for one based on 12 words
size_t use_size = sizeof(src.data);
size_t len = use_size;
while ((len > 0) && (! src.data[len - 1])) {
--len;
}
if (len <= sizeof(crypto::secret_key) / 2) // TODO refactor with elsewhere
{
use_size = use_size / 2;
}
for (unsigned int i=0; i < use_size / 4; i++, words += ' ')
{
uint32_t w1, w2, w3;

Expand Down
2 changes: 1 addition & 1 deletion src/mnemonics/electrum-words.h
Expand Up @@ -69,7 +69,7 @@ namespace crypto
* \return false if not a multiple of 3 words, or if word is not in the words list
*/
bool words_to_bytes(std::string words, crypto::secret_key& dst,
std::string &language_name);
std::string &language_name, size_t &num_words);

/*!
* \brief Converts bytes (secret key) to seed words.
Expand Down
10 changes: 6 additions & 4 deletions src/simplewallet/simplewallet.cpp
Expand Up @@ -201,6 +201,7 @@ std::string simple_wallet::get_commands_str()
bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
success_msg_writer() << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << std::endl;
success_msg_writer() << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key) << std::endl;

return true;
}
Expand Down Expand Up @@ -469,6 +470,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
if (m_wallet_file.empty()) m_wallet_file = m_generate_new; // alias for simplicity later

std::string old_language;
size_t num_words = 0;
// check for recover flag. if present, require electrum word list (only recovery option for now).
if (m_restore_deterministic_wallet)
{
Expand All @@ -488,14 +490,14 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
}

if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language))
if (!crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key, old_language, num_words))
{
fail_msg_writer() << "electrum-style word list failed verification";
return false;
}
}
bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet,
m_non_deterministic, testnet, old_language);
m_non_deterministic, testnet, old_language, num_words);
CHECK_AND_ASSERT_MES(r, false, "account creation failed");
}
else
Expand Down Expand Up @@ -581,7 +583,7 @@ std::string simple_wallet::get_mnemonic_language()

//----------------------------------------------------------------------------------------------------
bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key,
bool recover, bool two_random, bool testnet, const std::string &old_language)
bool recover, bool two_random, bool testnet, const std::string &old_language, size_t num_words)
{
bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) ||
crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed));
Expand Down Expand Up @@ -611,7 +613,7 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string
crypto::secret_key recovery_val;
try
{
recovery_val = m_wallet->generate(wallet_file, password, recovery_key, recover, two_random);
recovery_val = m_wallet->generate(wallet_file, password, recovery_key, recover, two_random, num_words);
message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: "
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet()) << std::endl << "view key: "
<< string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key);
Expand Down
2 changes: 1 addition & 1 deletion src/simplewallet/simplewallet.h
Expand Up @@ -75,7 +75,7 @@ namespace cryptonote
bool run_console_handler();

bool new_wallet(const std::string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key,
bool recover, bool two_random, bool testnet, const std::string &old_language);
bool recover, bool two_random, bool testnet, const std::string &old_language, size_t num_words);
bool open_wallet(const std::string &wallet_file, const std::string& password, bool testnet);
bool close_wallet();

Expand Down
49 changes: 42 additions & 7 deletions src/wallet/wallet2.cpp
Expand Up @@ -59,6 +59,9 @@ extern "C"
}
using namespace cryptonote;

// TODO CONFIG
extern int mode_key_generation;

namespace
{
void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file)
Expand Down Expand Up @@ -91,10 +94,44 @@ void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction
//----------------------------------------------------------------------------------------------------
bool wallet2::is_deterministic()
{
bool keys_deterministic = false;
crypto::secret_key second;
keccak((uint8_t *)&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));

const crypto::secret_key recovery_key = get_account().get_keys().m_seed;
bool half_seed = false;
size_t len = sizeof(recovery_key.data);
while ((len > 0) && (! recovery_key.data[len - 1])) {
--len;
}
if (len <= sizeof(crypto::secret_key) / 2) // TODO refactor with elsewhere
{
half_seed = true;
}
crypto::secret_key use_recovery_key;
if (! half_seed)
{
use_recovery_key = recovery_key;
}
else
{
if (mode_key_generation == 0)
{
use_recovery_key = recovery_key;
memcpy(use_recovery_key.data+16, use_recovery_key.data, 16); // if electrum 12-word seed, duplicate
}
else if (mode_key_generation == 1)
{
keccak((uint8_t *)&recovery_key.data, sizeof(crypto::secret_key) / 2, (uint8_t *)&use_recovery_key.data, sizeof(crypto::secret_key));
}
else
{
throw std::runtime_error("Wallet key generation mode not recognized");
}
}

keccak((uint8_t *)&use_recovery_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));
sc_reduce32((uint8_t *)&second);
bool keys_deterministic = memcmp(second.data,get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0;
keys_deterministic = memcmp(second.data, get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0;
return keys_deterministic;
}
//----------------------------------------------------------------------------------------------------
Expand All @@ -112,9 +149,7 @@ bool wallet2::get_seed(std::string& electrum_words)
return false;
}

crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_spend_secret_key, electrum_words, seed_language);

return true;
return crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_seed, electrum_words, seed_language);
}
/*!
* \brief Gets the seed language
Expand Down Expand Up @@ -629,7 +664,7 @@ bool wallet2::verify_password(const std::string& password)
* \return The secret key of the generated wallet
*/
crypto::secret_key wallet2::generate(const std::string& wallet_, const std::string& password,
const crypto::secret_key& recovery_param, bool recover, bool two_random)
const crypto::secret_key& recovery_param, bool recover, bool two_random, size_t num_words)
{
clear();
prepare_file_names(wallet_);
Expand All @@ -638,7 +673,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file);
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);

crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random);
crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random, num_words);

m_account_public_address = m_account.get_keys().m_account_address;

Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet2.h
Expand Up @@ -144,7 +144,7 @@ namespace tools
*/
crypto::secret_key generate(const std::string& wallet, const std::string& password,
const crypto::secret_key& recovery_param = crypto::secret_key(), bool recover = false,
bool two_random = false);
bool two_random = false, size_t num_words=0);
/*!
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
* \param wallet_name Name of wallet file (should exist)
Expand Down

0 comments on commit 35b1500

Please sign in to comment.