1
0
Fork 0
forked from lthn/blockchain
blockchain/src/currency_core/account.cpp
2021-03-18 22:35:11 +03:00

317 lines
No EOL
13 KiB
C++

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <fstream>
#include "include_base_utils.h"
#include "account.h"
#include "warnings.h"
#include "crypto/crypto.h"
#include "currency_core/currency_format_utils.h"
#include "common/mnemonic-encoding.h"
using namespace std;
//DISABLE_VS_WARNINGS(4244 4345)
namespace currency
{
//-----------------------------------------------------------------
account_base::account_base()
{
set_null();
}
//-----------------------------------------------------------------
void account_base::set_null()
{
// fill sensitive data with random bytes
crypto::generate_random_bytes(sizeof m_keys.spend_secret_key, &m_keys.spend_secret_key);
crypto::generate_random_bytes(sizeof m_keys.view_secret_key, &m_keys.view_secret_key);
if (m_keys_seed_binary.size())
crypto::generate_random_bytes(m_keys_seed_binary.size(), &m_keys_seed_binary[0]);
// clear
m_keys = account_keys();
m_creation_timestamp = 0;
m_keys_seed_binary.clear();
}
//-----------------------------------------------------------------
void account_base::generate(bool auditable /* = false */)
{
if (auditable)
m_keys.account_address.flags = ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE;
crypto::generate_seed_keys(m_keys.account_address.spend_public_key, m_keys.spend_secret_key, m_keys_seed_binary, BRAINWALLET_DEFAULT_SEED_SIZE);
crypto::dependent_key(m_keys.spend_secret_key, m_keys.view_secret_key);
if (!crypto::secret_key_to_public_key(m_keys.view_secret_key, m_keys.account_address.view_public_key))
throw std::runtime_error("Failed to create public view key");
m_creation_timestamp = time(NULL);
}
//-----------------------------------------------------------------
const account_keys& account_base::get_keys() const
{
return m_keys;
}
//-----------------------------------------------------------------
void crypt_with_pass(const void* scr_data, std::size_t src_length, void* dst_data, const std::string& password)
{
crypto::chacha8_key key = AUTO_VAL_INIT(key);
crypto::generate_chacha8_key(password, key);
crypto::hash pass_hash = crypto::cn_fast_hash(password.data(), password.size());
crypto::chacha8_iv iv = AUTO_VAL_INIT(iv);
CHECK_AND_ASSERT_THROW_MES(sizeof(pass_hash) >= sizeof(iv), "Invalid configuration: hash size is less than keys_file_data.iv");
iv = *((crypto::chacha8_iv*)&pass_hash);
crypto::chacha8(scr_data, src_length, key, iv, (char*)dst_data);
}
std::string account_base::get_seed_phrase(const std::string& password) const
{
if (m_keys_seed_binary.empty())
return "";
std::vector<unsigned char> processed_seed_binary = m_keys_seed_binary;
if (!password.empty())
{
//encrypt seed phrase binary data
crypt_with_pass(&m_keys_seed_binary[0], m_keys_seed_binary.size(), &processed_seed_binary[0], password);
}
std::string keys_seed_text = tools::mnemonic_encoding::binary2text(processed_seed_binary);
std::string timestamp_word = currency::get_word_from_timstamp(m_creation_timestamp, !password.empty());
// floor creation time to WALLET_BRAIN_DATE_QUANTUM to make checksum calculation stable
bool self_check_is_password_used = false;
uint64_t creation_timestamp_rounded = get_timstamp_from_word(timestamp_word, self_check_is_password_used);
CHECK_AND_ASSERT_THROW_MES(self_check_is_password_used == !password.empty(), "Account seed phrase internal error: password flag encoded wrong");
constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum
std::string binary_for_check_sum((const char*)&m_keys_seed_binary[0], m_keys_seed_binary.size());
binary_for_check_sum.append(password);
crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size());
*reinterpret_cast<uint64_t*>(&h) = creation_timestamp_rounded;
h = crypto::cn_fast_hash(&h, sizeof h);
uint64_t h_64 = *reinterpret_cast<uint64_t*>(&h);
uint16_t checksum = h_64 % (checksum_max + 1);
uint8_t auditable_flag = 0;
if (m_keys.account_address.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE)
auditable_flag = 1;
uint32_t auditable_flag_and_checksum = (auditable_flag & 1) | (checksum << 1);
std::string auditable_flag_and_checksum_word = tools::mnemonic_encoding::word_by_num(auditable_flag_and_checksum);
return keys_seed_text + " " + timestamp_word + " " + auditable_flag_and_checksum_word;
}
//-----------------------------------------------------------------
std::string account_base::get_tracking_seed() const
{
return get_public_address_str() + ":" +
epee::string_tools::pod_to_hex(m_keys.view_secret_key) +
(m_creation_timestamp ? ":" : "") + (m_creation_timestamp ? epee::string_tools::num_to_string_fast(m_creation_timestamp) : "");
}
//-----------------------------------------------------------------
bool account_base::restore_keys(const std::vector<unsigned char>& keys_seed_binary)
{
CHECK_AND_ASSERT_MES(keys_seed_binary.size() == BRAINWALLET_DEFAULT_SEED_SIZE, false, "wrong restore data size: " << keys_seed_binary.size());
crypto::keys_from_default(keys_seed_binary.data(), m_keys.account_address.spend_public_key, m_keys.spend_secret_key, keys_seed_binary.size());
crypto::dependent_key(m_keys.spend_secret_key, m_keys.view_secret_key);
bool r = crypto::secret_key_to_public_key(m_keys.view_secret_key, m_keys.account_address.view_public_key);
CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key for view key");
return true;
}
//-----------------------------------------------------------------
bool account_base::restore_from_seed_phrase(const std::string& seed_phrase, const std::string& seed_password)
{
//cut the last timestamp word from restore_dats
std::list<std::string> words;
boost::split(words, seed_phrase, boost::is_space());
std::string keys_seed_text, timestamp_word, auditable_flag_and_checksum_word;
if (words.size() == SEED_PHRASE_V1_WORDS_COUNT)
{
// 24 seed words + one timestamp word = 25 total
timestamp_word = words.back();
words.erase(--words.end());
keys_seed_text = boost::algorithm::join(words, " ");
}
else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT)
{
// 24 seed words + one timestamp word + one flags & checksum = 26 total
auditable_flag_and_checksum_word = words.back();
words.erase(--words.end());
timestamp_word = words.back();
words.erase(--words.end());
keys_seed_text = boost::algorithm::join(words, " ");
}
else
{
LOG_ERROR("Invalid seed words count: " << words.size());
return false;
}
uint64_t auditable_flag_and_checksum = UINT64_MAX;
if (!auditable_flag_and_checksum_word.empty())
{
try {
auditable_flag_and_checksum = tools::mnemonic_encoding::num_by_word(auditable_flag_and_checksum_word);
}
catch (...)
{
return false;
}
}
std::vector<unsigned char> keys_seed_binary = tools::mnemonic_encoding::text2binary(keys_seed_text);
std::vector<unsigned char> keys_seed_processed_binary = keys_seed_binary;
bool has_password = false;
try {
m_creation_timestamp = get_timstamp_from_word(timestamp_word, has_password);
}
catch (...)
{
return false;
}
//double check is password setting from timestamp word match with passed parameters
CHECK_AND_ASSERT_MES(has_password != seed_password.empty(), false, "Seed phrase password wrong interpretation");
if (has_password)
{
CHECK_AND_ASSERT_MES(!seed_password.empty(), false, "Seed phrase password wrong interpretation: internal error");
crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &keys_seed_processed_binary[0], seed_password);
}
CHECK_AND_ASSERT_MES(keys_seed_processed_binary.size(), false, "text2binary failed to convert the given text"); // don't prints event incorrect seed into the log for security
bool auditable_flag = false;
// check the checksum if checksum word provided
if (auditable_flag_and_checksum != UINT64_MAX)
{
auditable_flag = (auditable_flag_and_checksum & 1) != 0; // auditable flag is the lower 1 bit
uint16_t checksum = static_cast<uint16_t>(auditable_flag_and_checksum >> 1); // checksum -- everything else
constexpr uint16_t checksum_max = tools::mnemonic_encoding::NUMWORDS >> 1; // maximum value of checksum
std::string binary_for_check_sum((const char*)&keys_seed_processed_binary[0], keys_seed_processed_binary.size());
binary_for_check_sum.append(seed_password);
crypto::hash h = crypto::cn_fast_hash(binary_for_check_sum.data(), binary_for_check_sum.size());
*reinterpret_cast<uint64_t*>(&h) = m_creation_timestamp;
h = crypto::cn_fast_hash(&h, sizeof h);
uint64_t h_64 = *reinterpret_cast<uint64_t*>(&h);
uint16_t checksum_calculated = h_64 % (checksum_max + 1);
if (checksum != checksum_calculated)
{
LOG_PRINT_L0("seed phase has invalid checksum: " << checksum_calculated << ", while " << checksum << " is expected, check your words");
return false;
}
}
bool r = restore_keys(keys_seed_processed_binary);
CHECK_AND_ASSERT_MES(r, false, "restore_keys failed");
m_keys_seed_binary = keys_seed_processed_binary;
if (auditable_flag)
m_keys.account_address.flags |= ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE;
return true;
}
//-----------------------------------------------------------------
bool account_base::is_seed_tracking(const std::string& seed_phrase)
{
return seed_phrase.find(':') != std::string::npos;
}
//-----------------------------------------------------------------
bool account_base::is_seed_password_protected(const std::string& seed_phrase, bool& is_password_protected)
{
//cut the last timestamp word from restore_dats
std::list<std::string> words;
boost::split(words, seed_phrase, boost::is_space());
//let's validate each word
for (const auto& w: words)
{
if (!tools::mnemonic_encoding::valid_word(w))
return false;
}
std::string timestamp_word;
if (words.size() == SEED_PHRASE_V1_WORDS_COUNT)
{
// 24 seed words + one timestamp word = 25 total
timestamp_word = words.back();
}
else if (words.size() == SEED_PHRASE_V2_WORDS_COUNT)
{
// 24 seed words + one timestamp word + one flags & checksum = 26 total
words.erase(--words.end());
timestamp_word = words.back();
}
else
{
return false;
}
get_timstamp_from_word(timestamp_word, is_password_protected);
return true;
}
//-----------------------------------------------------------------
bool account_base::restore_from_tracking_seed(const std::string& tracking_seed)
{
set_null();
bool r = parse_tracking_seed(tracking_seed, m_keys.account_address, m_keys.view_secret_key, m_creation_timestamp);
return r;
}
//-----------------------------------------------------------------
std::string account_base::get_public_address_str() const
{
//TODO: change this code into base 58
return get_account_address_as_str(m_keys.account_address);
}
//-----------------------------------------------------------------
void account_base::make_account_watch_only()
{
// keep only:
// timestamp
// view pub & spend pub + flags (public address)
// view sec
// store to local tmp
uint64_t local_ts = m_creation_timestamp;
account_public_address local_addr = m_keys.account_address;
crypto::secret_key local_view_sec = m_keys.view_secret_key;
// clear
set_null();
// restore
m_creation_timestamp = local_ts;
m_keys.account_address = local_addr;
m_keys.view_secret_key = local_view_sec;
}
//-----------------------------------------------------------------
std::string transform_addr_to_str(const account_public_address& addr)
{
return get_account_address_as_str(addr);
}
//-----------------------------------------------------------------
account_public_address transform_str_to_addr(const std::string& str)
{
account_public_address ad = AUTO_VAL_INIT(ad);
if (!get_account_address_from_str(ad, str))
{
CHECK_AND_ASSERT_THROW_MES(false, "cannot parse address from string: " << str);
}
return ad;
}
}