Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
67 changes: 67 additions & 0 deletions src/crypto/chacha.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#if defined(__cplusplus)
#include <memory.h>
#include <vector>
namespace crypto
{
// all C function share the same space of names regardless of 'namespace', but here we put it into crypto just for convenience -- sowle
Expand Down Expand Up @@ -117,6 +118,72 @@ namespace crypto
chacha20(&pod_object, sizeof pod_object, key, iv, &pod_object);
}

// ROMix-Keccak memory-hard password stretching built purely on Keccak-256
inline void derive_key_romix_keccak(const char (&hdss)[32], const void* password_data, size_t password_data_size,
const void* salt_data, size_t salt_data_size, uint8_t N_log2, uint8_t phase2_log2_reduction, uint8_t (&out_stretched)[32])
{
if (N_log2 < 10)
N_log2 = 10; // 32kb ram min
if (N_log2 > 30)
N_log2 = 30; // 32gb ram max
const uint64_t N = (uint64_t)1 << N_log2;
const uint64_t mask = N - 1;

if (phase2_log2_reduction >= N_log2)
phase2_log2_reduction = (uint8_t)(N_log2 - 1);
const uint64_t phase2_iters = N >> phase2_log2_reduction;

// phase 1
std::vector<uint8_t> V(N * 32);
{
std::string seed; // [hdss 32B][salt 16B][password ?B]
seed.reserve(32 + salt_data_size + password_data_size);
seed.append(hdss, 32);
if (salt_data_size)
seed.append(reinterpret_cast<const char*>(salt_data), salt_data_size);
if (password_data_size)
seed.append(reinterpret_cast<const char*>(password_data), password_data_size);
keccak(reinterpret_cast<const uint8_t*>(seed.data()), (int)seed.size(), V.data(), 32);
if (!seed.empty())
memset(const_cast<char*>(seed.data()), 0, seed.size());
}


uint8_t block_in[40]; // 32 + i(8) = 40
for (uint64_t i = 1; i < N; ++i)
{
memcpy(block_in, V.data() + (i - 1) * 32, 32);
for (int b = 0; b < 8; ++b)
block_in[32 + b] = (uint8_t)(i >> (8 * b));
keccak(block_in, 40, V.data() + i * 32, 32);
}

// phase 2 - data dependent random walk the read idx depends on the current state X, so V cannot be streamed
uint8_t X[32];
memcpy(X, V.data() + (N - 1) * 32, 32);
uint8_t mix[40];
for (uint64_t j = 0; j < phase2_iters; ++j)
{
uint64_t idx_raw = 0;
for (int b = 0; b < 8; ++b)
idx_raw |= (uint64_t)X[b] << (8 * b);
const uint64_t idx = idx_raw & mask;
const uint8_t* Vi = V.data() + idx * 32;
for (int k = 0; k < 32; ++k)
mix[k] = X[k] ^ Vi[k];
for (int b = 0; b < 8; ++b)
mix[32 + b] = (uint8_t)(j >> (8 * b));
keccak(mix, 40, X, 32);
}

memcpy(out_stretched, X, 32);

// wipe buffs
memset(V.data(), 0, V.size());
memset(X, 0, sizeof(X));
memset(mix, 0, sizeof(mix));
}


//
// legacy functions
Expand Down
1 change: 1 addition & 0 deletions src/currency_core/crypto_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ static_assert(crypto::bpp_crypto_trait_ZC_out::c_bpp_values_max == CURRENCY_TX_M
#define CRYPTO_HDS_GW_OUT_AMOUNT_MASK "ZANO_HDS_GW_OUT_AMOUNT_MASK____"
#define CRYPTO_HDS_CHACHA_WALLET_HEADER "ZANO_HDS_CHACHA_WALLET_HEADER__"
#define CRYPTO_HDS_CHACHA_WALLET_BODY "ZANO_HDS_CHACHA_WALLET_BODY____"
#define CRYPTO_HDS_WALLET_KDF_ROMIX "ZANO_HDS_WALLET_KDF_ROMIX______"
7 changes: 7 additions & 0 deletions src/currency_core/currency_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@
#define WALLET_FILE_BINARY_HEADER_VERSION_INITAL 1000
#define WALLET_FILE_BINARY_HEADER_VERSION_2 1001
#define WALLET_FILE_BINARY_HEADER_VERSION_3 1002
#define WALLET_FILE_BINARY_HEADER_VERSION_4 1003

#define WALLET_KDF_ALGO_NONE 0
#define WALLET_KDF_ALGO_ROMIX_KECCAK 1
#define WALLET_KDF_ROMIX_N_LOG2 20 //phase 1: the buffer size is V = 2^(N_log2) * 32 bytes, where N_log2 = 20 -> 1 million blocks * 32 bytes = 32 MiB
#define WALLET_KDF_ROMIX_PHASE2_LOG2_REDUCTION 3 //phase 2: iteration reduction: 0 = full N phase 2 iterations, 1 = N/2 iterations 2 = N/4, 3 = N/8, 4 = N/16 ... (still 32 MiB at N_log2=20)
#define WALLET_KDF_SALT_SIZE 16

#define WALLET_FILE_MAX_KEYS_SIZE 10000 //
#define WALLET_BRAIN_DATE_OFFSET 1543622400
Expand Down
75 changes: 63 additions & 12 deletions src/wallet/wallet2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3013,7 +3013,25 @@ bool wallet2::reset_all()
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, bool store_as_watch_only /* = false */)
namespace
{
std::string derive_wallet_password_stretched(const std::string& password, const tools::wallet2::keys_file_data& kf_data)
{
if (kf_data.kdf_algo == WALLET_KDF_ALGO_NONE)
return password;
CHECK_AND_ASSERT_THROW_MES(kf_data.kdf_algo == WALLET_KDF_ALGO_ROMIX_KECCAK, "unsupported wallet KDF algo: " << (int)kf_data.kdf_algo);
CHECK_AND_ASSERT_THROW_MES(kf_data.kdf_salt.size() == WALLET_KDF_SALT_SIZE, "unexpected wallet KDF salt size: " << kf_data.kdf_salt.size());

uint8_t stretched[32];
crypto::derive_key_romix_keccak(CRYPTO_HDS_WALLET_KDF_ROMIX, password.data(), password.size(),
kf_data.kdf_salt.data(), kf_data.kdf_salt.size(), kf_data.kdf_N_log2, kf_data.kdf_phase2_log2_reduction, stretched);
std::string result(reinterpret_cast<const char*>(stretched), sizeof(stretched));
memset(stretched, 0, sizeof(stretched));
return result;
}
}
//----------------------------------------------------------------------------------------------------
bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, bool store_as_watch_only /* = false */, std::string* out_body_password /* = nullptr */)
{
currency::account_base acc = m_account;
if (store_as_watch_only)
Expand All @@ -3023,18 +3041,28 @@ bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2
bool r = epee::serialization::store_t_to_binary(acc, account_data);
WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys");


keys_file_data.kdf_algo = WALLET_KDF_ALGO_ROMIX_KECCAK;
keys_file_data.kdf_N_log2 = WALLET_KDF_ROMIX_N_LOG2;
keys_file_data.kdf_phase2_log2_reduction = WALLET_KDF_ROMIX_PHASE2_LOG2_REDUCTION;
keys_file_data.kdf_salt.resize(WALLET_KDF_SALT_SIZE);
crypto::generate_random_bytes(keys_file_data.kdf_salt.size(), keys_file_data.kdf_salt.data());

// memory-hard stretch
const std::string stretched = derive_wallet_password_stretched(password, keys_file_data);

crypto::chacha_key key;
crypto::chacha_generate_key_and_iv(CRYPTO_HDS_CHACHA_WALLET_HEADER, password.data(), password.size(), 0, key);
crypto::chacha_generate_key_and_iv(CRYPTO_HDS_CHACHA_WALLET_HEADER, stretched.data(), stretched.size(), 0, key);
std::string cipher;
cipher.resize(account_data.size());
keys_file_data.iv = crypto::rand<crypto::chacha_iv>();
keys_file_data.iv = crypto::rand<crypto::chacha_iv>();
crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]);
keys_file_data.account_data = cipher;

r = ::serialization::dump_binary(keys_file_data, buff);

if (out_body_password)
*out_body_password = stretched;

return true;
}
//----------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -3128,7 +3156,7 @@ bool wallet2::prepare_file_names(const std::wstring& file_path)
return true;
}
//----------------------------------------------------------------------------------------------------
void wallet2::load_keys(const std::string& buff, const std::string& password, uint64_t file_signature, keys_file_data& kf_data)
void wallet2::load_keys(const std::string& buff, const std::string& password, uint64_t file_signature, keys_file_data& kf_data, std::string* out_body_password)
{
bool r = false;
if (file_signature == WALLET_FILE_SIGNATURE_OLD)
Expand All @@ -3143,22 +3171,32 @@ void wallet2::load_keys(const std::string& buff, const std::string& password, ui
}
THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize");
std::string account_data;

std::string body_password = password;

if (kf_data.version <= 1)
{
crypto::chacha_key key;
crypto::generate_chacha_key_legacy(password, key);
account_data.resize(kf_data.account_data.size());
crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]);
}
else
else if (kf_data.version == 2)
{
//version 2
//legacy version 2
crypto::chacha_key key;
crypto::chacha_generate_key_and_iv(CRYPTO_HDS_CHACHA_WALLET_HEADER, password.data(), password.size(), 0, key);
account_data.resize(kf_data.account_data.size());
crypto::chacha20(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]);
}
else
{
// version 3: ROMix-like Keccak password stretching drives the existing chacha KDF
body_password = derive_wallet_password_stretched(password, kf_data);
crypto::chacha_key key;
crypto::chacha_generate_key_and_iv(CRYPTO_HDS_CHACHA_WALLET_HEADER, body_password.data(), body_password.size(), 0, key);
account_data.resize(kf_data.account_data.size());
crypto::chacha20(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]);
}

const currency::account_keys& keys = m_account.get_keys();
r = epee::serialization::load_t_from_binary(m_account, account_data);
Expand All @@ -3172,6 +3210,8 @@ void wallet2::load_keys(const std::string& buff, const std::string& password, ui
WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file));
tools::error::throw_wallet_ex<error::invalid_password>(std::string(__FILE__ ":" STRINGIZE(__LINE__)));
}
if (out_body_password)
*out_body_password = body_password;
init_log_prefix();
}
//----------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -3294,7 +3334,8 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password)
keys_buff.resize(wbh.m_cb_keys);
data_file.read((char*)keys_buff.data(), wbh.m_cb_keys);
wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data);
load_keys(keys_buff, password, wbh.m_signature, kf_data);
std::string body_password;
load_keys(keys_buff, password, wbh.m_signature, kf_data, &body_password);

bool need_to_resync = false;
if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_INITAL)
Expand All @@ -3321,6 +3362,15 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password)
need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in);
WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_3 (need_to_resync=" << need_to_resync << ")");
}
else if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_4)
{
tools::encrypt_chacha20_in_filter decrypt_filter(body_password, kf_data.iv, CRYPTO_HDS_CHACHA_WALLET_BODY);
boost::iostreams::filtering_istream in;
in.push(decrypt_filter);
in.push(data_file);
need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in);
WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_4 (need_to_resync=" << need_to_resync << ")");
}
else
{
WLT_LOG_L0("Unknown wallet body version(" << wbh.m_ver << "), resync initiated.");
Expand Down Expand Up @@ -3373,16 +3423,17 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor

//prepare data
std::string keys_buff;
std::string body_password;
wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data);
bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only);
bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only, &body_password);
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save);

//store data
wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh);
wbh.m_signature = WALLET_FILE_SIGNATURE_V2;
wbh.m_cb_keys = keys_buff.size();
//@#@ change it to proper
wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_3;
wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_4;
std::string header_buff((const char*)&wbh, sizeof(wbh));

uint64_t ts = m_core_runtime_config.get_core_time();
Expand All @@ -3398,7 +3449,7 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor

WLT_LOG_L0("Storing to temporary file " << tmp_file_path.string() << " ...");
//creating encryption stream
tools::encrypt_chacha20_out_filter decrypt_filter(m_password, keys_file_data.iv, CRYPTO_HDS_CHACHA_WALLET_BODY);
tools::encrypt_chacha20_out_filter decrypt_filter(body_password, keys_file_data.iv, CRYPTO_HDS_CHACHA_WALLET_BODY);
boost::iostreams::filtering_ostream out;
out.push(decrypt_filter);
out.push(data_file);
Expand Down
18 changes: 15 additions & 3 deletions src/wallet/wallet2.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ namespace tools
crypto::chacha_iv iv;
std::string account_data;

uint8_t kdf_algo{0};
uint8_t kdf_N_log2{0};
std::string kdf_salt;
uint8_t kdf_phase2_log2_reduction{0};

static keys_file_data from_old(const keys_file_data_old& v)
{
keys_file_data result = AUTO_VAL_INIT(result);
Expand All @@ -301,11 +306,18 @@ namespace tools
return result;
}

DEFINE_SERIALIZATION_VERSION(2)
DEFINE_SERIALIZATION_VERSION(4)
BEGIN_SERIALIZE_OBJECT()
VERSION_ENTRY(version)
FIELD(iv)
FIELD(account_data)
if (version >= 3)
{
FIELD(kdf_algo)
FIELD(kdf_N_log2)
FIELD(kdf_salt)
FIELD(kdf_phase2_log2_reduction)
}
END_SERIALIZE()
};

Expand Down Expand Up @@ -383,7 +395,7 @@ namespace tools
void store(const std::wstring& path);
void store(const std::wstring& path, const std::string& password);
void store_watch_only(const std::wstring& path, const std::string& password) const;
bool store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, bool store_as_watch_only = false);
bool store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, bool store_as_watch_only = false, std::string* out_body_password = nullptr);
std::wstring get_wallet_path()const { return m_wallet_file; }
std::string get_wallet_password()const { return m_password; }
currency::account_base& get_account() { return m_account; }
Expand Down Expand Up @@ -808,7 +820,7 @@ namespace tools
// ------------------------------------------------------------------------------------
void add_transfers_to_expiration_list(const std::vector<uint64_t>& selected_transfers, const std::vector<payment_details_subtransfer>& received, uint64_t expiration, const crypto::hash& related_tx_id);
void remove_transfer_from_expiration_list(uint64_t transfer_index);
void load_keys(const std::string& keys_file_name, const std::string& password, uint64_t file_signature, keys_file_data& kf_data);
void load_keys(const std::string& keys_file_name, const std::string& password, uint64_t file_signature, keys_file_data& kf_data, std::string* out_body_password = nullptr);
void process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc);
void process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b, const std::vector<uint64_t>* pglobal_indexes);
void fetch_tx_global_indixes(const currency::transaction& tx, std::vector<uint64_t>& goutputs_indexes);
Expand Down
Loading