diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 583 |
1 files changed, 428 insertions, 155 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d3f3d4880..3e2ccd1ff 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -48,6 +48,7 @@ using namespace epee; #include "wallet_rpc_helpers.h" #include "wallet2.h" #include "cryptonote_basic/cryptonote_format_utils.h" +#include "net/parse.h" #include "rpc/core_rpc_server_commands_defs.h" #include "rpc/core_rpc_server_error_codes.h" #include "rpc/rpc_payment_signature.h" @@ -102,8 +103,8 @@ using namespace cryptonote; // used to target a given block weight (additional outputs may be added on top to build fee) #define TX_WEIGHT_TARGET(bytes) (bytes*2/3) -#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" -#define SIGNED_TX_PREFIX "Monero signed tx set\004" +#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\005" +#define SIGNED_TX_PREFIX "Monero signed tx set\005" #define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" #define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone @@ -242,6 +243,22 @@ namespace add_reason(reason, "tx was not relayed"); return reason; } + + size_t get_num_outputs(const std::vector<cryptonote::tx_destination_entry> &dsts, const std::vector<tools::wallet2::transfer_details> &transfers, const std::vector<size_t> &selected_transfers) + { + size_t outputs = dsts.size(); + uint64_t needed_money = 0; + for (const auto& dt: dsts) + needed_money += dt.amount; + uint64_t found_money = 0; + for(size_t idx: selected_transfers) + found_money += transfers[idx].amount(); + if (found_money != needed_money) + ++outputs; // change + if (outputs < 2) + ++outputs; // extra 0 dummy output + return outputs; + } } namespace @@ -440,30 +457,14 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl ); } - boost::asio::ip::tcp::endpoint proxy{}; + std::string proxy; if (use_proxy) { - namespace ip = boost::asio::ip; - - const auto proxy_address = command_line::get_arg(vm, opts.proxy); - - boost::string_ref proxy_port{proxy_address}; - boost::string_ref proxy_host = proxy_port.substr(0, proxy_port.rfind(":")); - if (proxy_port.size() == proxy_host.size()) - proxy_host = "127.0.0.1"; - else - proxy_port = proxy_port.substr(proxy_host.size() + 1); - - uint16_t port_value = 0; + proxy = command_line::get_arg(vm, opts.proxy); THROW_WALLET_EXCEPTION_IF( - !epee::string_tools::get_xtype_from_string(port_value, std::string{proxy_port}), + !net::get_tcp_endpoint(proxy), tools::error::wallet_internal_error, - std::string{"Invalid port specified for --"} + opts.proxy.name - ); - - boost::system::error_code error{}; - proxy = ip::tcp::endpoint{ip::address::from_string(std::string{proxy_host}, error), port_value}; - THROW_WALLET_EXCEPTION_IF(bool(error), tools::error::wallet_internal_error, std::string{"Invalid IP address specified for --"} + opts.proxy.name); + std::string{"Invalid address specified for --"} + opts.proxy.name); } boost::optional<bool> trusted_daemon; @@ -488,7 +489,10 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl } std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); - wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, std::move(ssl_options)); + if (!wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, std::move(ssl_options))) + { + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to initialize the wallet")); + } boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); wallet->get_message_store().set_options(vm); @@ -807,7 +811,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) { size_t size = 0; @@ -841,8 +845,11 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra else size += (2*64*32+32+64*32) * n_outputs; - // MGs - size += n_inputs * (64 * (mixin+1) + 32); + // MGs/CLSAGs + if (clsag) + size += n_inputs * (32 * (mixin+1) + 64); + else + size += n_inputs * (64 * (mixin+1) + 32); // mixRing - not serialized, can be reconstructed /* size += 2 * 32 * (mixin+1) * n_inputs; */ @@ -860,17 +867,17 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra return size; } -size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) { if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof); + return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); else return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; } -uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof) +uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) { - size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); if (use_rct && bulletproof && n_outputs > 2) { const uint64_t bp_base = 368; @@ -891,6 +898,11 @@ uint8_t get_bulletproof_fork() return 8; } +uint8_t get_clsag_fork() +{ + return HF_VERSION_CLSAG; +} + uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) { if (use_per_byte_fee) @@ -1125,7 +1137,7 @@ void wallet_device_callback::on_progress(const hw::device_progress& event) } wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory): - m_http_client(std::move(http_client_factory->create())), + m_http_client(http_client_factory->create()), m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_upper_transaction_weight_limit(0), @@ -1195,6 +1207,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_offline(false), m_rpc_version(0), m_export_format(ExportFormat::Binary), + m_load_deprecated_formats(false), m_credits_target(0) { set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); @@ -1325,18 +1338,17 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u return ret; } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) +bool wallet2::set_proxy(const std::string &address) { + return m_http_client->set_proxy(address); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, const std::string &proxy_address, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) +{ + CHECK_AND_ASSERT_MES(set_proxy(proxy_address), false, "failed to set proxy address"); m_checkpoints.init_default_checkpoints(m_nettype); m_is_initialized = true; m_upper_transaction_weight_limit = upper_transaction_weight_limit; - if (proxy != boost::asio::ip::tcp::endpoint{}) - { - epee::net_utils::http::abstract_http_client* abstract_http_client = m_http_client.get(); - epee::net_utils::http::http_simple_client* http_simple_client = dynamic_cast<epee::net_utils::http::http_simple_client*>(abstract_http_client); - CHECK_AND_ASSERT_MES(http_simple_client != nullptr, false, "http_simple_client must be used to set proxy"); - http_simple_client->set_connector(net::socks::connector{std::move(proxy)}); - } return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- @@ -1764,6 +1776,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & case rct::RCTTypeSimple: case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: + case rct::RCTTypeCLSAG: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); @@ -3916,6 +3929,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_export_format); json.AddMember("export_format", value2, json.GetAllocator()); + value2.SetInt(m_load_deprecated_formats); + json.AddMember("load_deprecated_formats", value2, json.GetAllocator()); + value2.SetUint(1); json.AddMember("encrypted_secret_keys", value2, json.GetAllocator()); @@ -4085,6 +4101,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_original_keys_available = false; m_export_format = ExportFormat::Binary; + m_load_deprecated_formats = false; m_device_name = ""; m_device_derivation_path = ""; m_key_device_type = hw::device::device_type::SOFTWARE; @@ -4265,6 +4282,9 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, export_format, ExportFormat, Int, false, Binary); m_export_format = field_export_format; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, load_deprecated_formats, int, Int, false, false); + m_load_deprecated_formats = field_load_deprecated_formats; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string()); if (m_device_name.empty()) { @@ -5058,7 +5078,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor 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)); }); + 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(); @@ -5614,10 +5634,26 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]); try { - std::stringstream iss; - iss << cache_data; - boost::archive::portable_binary_iarchive ar(iss); - ar >> *this; + bool loaded = false; + + try + { + std::stringstream iss; + iss << cache_data; + binary_archive<false> ar(iss); + if (::serialization::serialize(ar, *this)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } + catch(...) { } + + if (!loaded) + { + std::stringstream iss; + iss << cache_data; + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } } catch(...) { @@ -5714,7 +5750,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass try { if (use_fs) - m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); + m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); } catch (const std::exception &e) { @@ -5904,8 +5940,9 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epe try { std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << *this; + binary_archive<true> ar(oss); + if (!::serialization::serialize(ar, *this)) + return boost::none; boost::optional<wallet2::cache_file_data> cache_file_data = (wallet2::cache_file_data) {}; cache_file_data.get().cache_data = oss.str(); @@ -6512,8 +6549,8 @@ void wallet2::commit_tx(pending_tx& ptx) add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices); if (store_tx_info() && ptx.tx_key != crypto::null_skey) { - m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); - m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + m_tx_keys[txid] = ptx.tx_key; + m_additional_tx_keys[txid] = ptx.additional_tx_keys; } LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); @@ -6567,10 +6604,11 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c txs.transfers = export_outputs(); // save as binary std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); + binary_archive<true> ar(oss); try { - ar << txs; + if (!::serialization::serialize(ar, txs)) + return std::string(); } catch (...) { @@ -6614,6 +6652,11 @@ bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsi s = s.substr(1); if (version == '\003') { + if (!m_load_deprecated_formats) + { + LOG_PRINT_L0("Not loading deprecated format"); + return false; + } try { std::istringstream iss(s); @@ -6628,6 +6671,11 @@ bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsi } else if (version == '\004') { + if (!m_load_deprecated_formats) + { + LOG_PRINT_L0("Not loading deprecated format"); + return false; + } try { s = decrypt_with_view_secret_key(s); @@ -6649,6 +6697,26 @@ bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsi return false; } } + else if (version == '\005') + { + try { s = decrypt_with_view_secret_key(s); } + catch(const std::exception &e) { LOG_PRINT_L0("Failed to decrypt unsigned tx: " << e.what()); return false; } + try + { + std::istringstream iss(s); + binary_archive<false> ar(iss); + if (!::serialization::serialize(ar, exported_txs)) + { + LOG_PRINT_L0("Failed to parse data from unsigned tx"); + return false; + } + } + catch (...) + { + LOG_PRINT_L0("Failed to parse data from unsigned tx"); + return false; + } + } else { LOG_PRINT_L0("Unsupported version in unsigned tx"); @@ -6702,8 +6770,8 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin if (store_tx_info() && tx_key != crypto::null_skey) { const crypto::hash txid = get_transaction_hash(ptx.tx); - m_tx_keys.insert(std::make_pair(txid, tx_key)); - m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys)); + m_tx_keys[txid] = tx_key; + m_additional_tx_keys[txid] = additional_tx_keys; } std::string key_images; @@ -6846,10 +6914,11 @@ std::string wallet2::sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vec // save as binary std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); + binary_archive<true> ar(oss); try { - ar << signed_txes; + if (!::serialization::serialize(ar, signed_txes)) + return std::string(); } catch(...) { @@ -6898,6 +6967,11 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too s = s.substr(1); if (version == '\003') { + if (!m_load_deprecated_formats) + { + LOG_PRINT_L0("Not loading deprecated format"); + return false; + } try { std::istringstream iss(s); @@ -6912,6 +6986,11 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too } else if (version == '\004') { + if (!m_load_deprecated_formats) + { + LOG_PRINT_L0("Not loading deprecated format"); + return false; + } try { s = decrypt_with_view_secret_key(s); @@ -6933,6 +7012,26 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too return false; } } + else if (version == '\005') + { + try { s = decrypt_with_view_secret_key(s); } + catch (const std::exception &e) { LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what()); return false; } + try + { + std::istringstream iss(s); + binary_archive<false> ar(iss); + if (!::serialization::serialize(ar, signed_txs)) + { + LOG_PRINT_L0("Failed to deserialize signed transaction"); + return false; + } + } + catch (const std::exception &e) + { + LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what()); + return false; + } + } else { LOG_PRINT_L0("Unsupported version in signed transaction"); @@ -6984,10 +7083,11 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs) // save as binary std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); + binary_archive<true> ar(oss); try { - ar << txs; + if (!::serialization::serialize(ar, txs)) + return std::string(); } catch (...) { @@ -7051,13 +7151,29 @@ bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx LOG_PRINT_L0("Failed to decrypt multisig tx data: " << e.what()); return false; } + bool loaded = false; try { std::istringstream iss(multisig_tx_st); - boost::archive::portable_binary_iarchive ar(iss); - ar >> exported_txs; + binary_archive<false> ar(iss); + if (::serialization::serialize(ar, exported_txs)) + if (::serialization::check_stream_state(ar)) + loaded = true; } - catch (...) + catch (...) {} + try + { + if (!loaded && m_load_deprecated_formats) + { + std::istringstream iss(multisig_tx_st); + boost::archive::portable_binary_iarchive ar(iss); + ar >> exported_txs; + loaded = true; + } + } + catch(...) {} + + if (!loaded) { LOG_PRINT_L0("Failed to parse multisig tx data"); return false; @@ -7103,8 +7219,8 @@ bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { - m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); - m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + m_tx_keys[txid] = ptx.tx_key; + m_additional_tx_keys[txid] = ptx.additional_tx_keys; } } } @@ -7224,8 +7340,8 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto const crypto::hash txid = get_transaction_hash(ptx.tx); if (store_tx_info()) { - m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); - m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); + m_tx_keys[txid] = ptx.tx_key; + m_additional_tx_keys[txid] = ptx.additional_tx_keys; } txids.push_back(txid); } @@ -7263,16 +7379,16 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const { if (use_per_byte_fee) { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); } else { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); } } @@ -7460,7 +7576,7 @@ uint32_t wallet2::adjust_priority(uint32_t priority) getbh_req.client = get_client_signature(); bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, *m_http_client, rpc_timeout); THROW_ON_RPC_RESPONSE_ERROR(r, {}, getbh_res, "getblockheadersrange", error::get_blocks_error, get_rpc_status(getbh_res.status)); - check_rpc_cost("/sendrawtransaction", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER); + check_rpc_cost("/getblockheadersrange", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER); } if (getbh_res.headers.size() != N) @@ -8975,7 +9091,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.construction_data.extra = tx.extra; ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; - ptx.construction_data.rct_config = { tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1}; + ptx.construction_data.rct_config = { + tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, + use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 + }; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs ptx.construction_data.subaddr_account = subaddr_account; @@ -9544,8 +9663,8 @@ bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const cr bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index) { // Lookup key image from cache - std::map<uint64_t, crypto::key_image> index_keyimage_map; - std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key); + serializable_map<uint64_t, crypto::key_image> index_keyimage_map; + serializable_unordered_map<crypto::public_key, serializable_map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key); if(found_pub_key != m_key_image_cache.end()) { // pub key found. key image for index cached? index_keyimage_map = found_pub_key->second; @@ -9661,9 +9780,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_rct = use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 + bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 }; const uint64_t base_fee = get_base_fee(); @@ -9699,7 +9819,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through - const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof)); + const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9717,8 +9837,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Candidate subaddress index for spending: " << i); // determine threshold for fractional amount - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -9815,7 +9935,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); + uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { @@ -9927,7 +10047,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << @@ -9939,7 +10059,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); @@ -9963,7 +10083,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); } @@ -9973,7 +10093,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); + const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); uint64_t inputs = 0, outputs = needed_fee; for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); @@ -10222,10 +10343,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below // determine threshold for fractional amount const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -10331,9 +10453,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, + bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, }; const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -10362,7 +10485,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton uint64_t fee_dust_threshold; if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); } else @@ -10393,14 +10516,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_weight_limit); - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); if (try_tx) { cryptonote::transaction test_tx; pending_tx test_ptx; - needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask); + const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); + needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); // add N - 1 outputs for correct initial fee estimation for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) @@ -10915,7 +11039,7 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s return true; } //---------------------------------------------------------------------------------------------------- -void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys) +void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress) { // fetch tx from daemon and check if secret keys agree with corresponding public keys COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); @@ -10956,13 +11080,23 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ found = true; break; } + // when sent to a single subaddress, the derivation is different + if (single_destination_subaddress) + { + calculated_pub_key = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_destination_subaddress->m_spend_public_key), rct::sk2rct(tx_key))); + if (calculated_pub_key == pub_key_field.pub_key) + { + found = true; + break; + } + } } THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, "Given tx secret key doesn't agree with the tx public key in the blockchain"); tx_extra_additional_pub_keys additional_tx_pub_keys; find_tx_extra_field_by_type(tx_extra_fields, additional_tx_pub_keys); THROW_WALLET_EXCEPTION_IF(additional_tx_keys.size() != additional_tx_pub_keys.data.size(), error::wallet_internal_error, "The number of additional tx secret keys doesn't agree with the number of additional tx public keys in the blockchain" ); - m_tx_keys.insert(std::make_pair(txid, tx_key)); - m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys)); + m_tx_keys[txid] = tx_key; + m_additional_tx_keys[txid] = additional_tx_keys; } //---------------------------------------------------------------------------------------------------- std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) @@ -11252,7 +11386,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::secret_key scalar1; crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); @@ -11425,7 +11559,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); } } - sig_str = std::string("OutProofV1"); + sig_str = std::string("OutProofV2"); } else { @@ -11461,7 +11595,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); } } - sig_str = std::string("InProofV1"); + sig_str = std::string("InProofV2"); } const size_t num_sigs = shared_secret.size(); @@ -11540,8 +11674,14 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const { + // InProofV1, InProofV2, OutProofV1, OutProofV2 const bool is_out = sig_str.substr(0, 3) == "Out"; - const std::string header = is_out ? "OutProofV1" : "InProofV1"; + const std::string header = is_out ? sig_str.substr(0,10) : sig_str.substr(0,9); + int version = 2; // InProofV2 + if (is_out && sig_str.substr(8,2) == "V1") version = 1; // OutProofV1 + else if (is_out) version = 2; // OutProofV2 + else if (sig_str.substr(7,2) == "V1") version = 1; // InProofV1 + const size_t header_len = header.size(); THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, "Signature header check error"); @@ -11588,27 +11728,27 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote if (is_out) { good_signature[0] = is_subaddress ? - crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : - crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) : + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version); for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { good_signature[i + 1] = is_subaddress ? - crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : - crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) : + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1], version); } } else { good_signature[0] = is_subaddress ? - crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0]) : - crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0]); + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version); for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { good_signature[i + 1] = is_subaddress ? - crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : - crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1]); + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1], version); } } @@ -11727,7 +11867,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, } // collect all subaddress spend keys that received those outputs and generate their signatures - std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + serializable_unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; for (const cryptonote::subaddress_index &index : subaddr_indices) { crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key; @@ -11744,9 +11884,10 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, // serialize & encode std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << proofs << subaddr_spendkeys; - return "ReserveProofV1" + tools::base58::encode(oss.str()); + binary_archive<true> ar(oss); + THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, proofs), error::wallet_internal_error, "Failed to serialize proof"); + THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, subaddr_spendkeys), error::wallet_internal_error, "Failed to serialize proof"); + return "ReserveProofV2" + tools::base58::encode(oss.str()); } bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent) @@ -11755,19 +11896,39 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old"); - static constexpr char header[] = "ReserveProofV1"; - THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error, + static constexpr char header_v1[] = "ReserveProofV1"; + static constexpr char header_v2[] = "ReserveProofV2"; // assumes same length as header_v1 + THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header_v1) && !boost::string_ref{sig_str}.starts_with(header_v2), error::wallet_internal_error, "Signature header check error"); + int version = 2; // assume newest version + if (boost::string_ref{sig_str}.starts_with(header_v1)) + version = 1; + else if (boost::string_ref{sig_str}.starts_with(header_v2)) + version = 2; std::string sig_decoded; - THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header_v1)), sig_decoded), error::wallet_internal_error, "Signature decoding error"); - std::istringstream iss(sig_decoded); - boost::archive::portable_binary_iarchive ar(iss); + bool loaded = false; std::vector<reserve_proof_entry> proofs; - std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; - ar >> proofs >> subaddr_spendkeys; + serializable_unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys; + try + { + std::istringstream iss(sig_decoded); + binary_archive<false> ar(iss); + if (::serialization::serialize_noeof(ar, proofs)) + if (::serialization::serialize_noeof(ar, subaddr_spendkeys)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } + catch(...) {} + if (!loaded && m_load_deprecated_formats) + { + std::istringstream iss(sig_decoded); + boost::archive::portable_binary_iarchive ar(iss); + ar >> proofs >> subaddr_spendkeys.parent(); + } THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error, "The given address isn't found in the proof"); @@ -11841,9 +12002,9 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); // check singature for shared secret - ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig); + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig, version); if (!ok && additional_tx_pub_keys.size() == tx.vout.size()) - ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig); + ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig, version); if (!ok) return false; @@ -11869,7 +12030,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::secret_key shared_secret; crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); amount = rct::h2d(ecdh_info.amount); } total += amount; @@ -12006,7 +12167,7 @@ std::string wallet2::get_description() const return ""; } -const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() +const std::pair<serializable_map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() { // ensure consistency if (m_account_tags.second.size() != get_num_subaddress_accounts()) @@ -12046,51 +12207,130 @@ void wallet2::set_account_tag_description(const std::string& tag, const std::str m_account_tags.first[tag] = description; } -std::string wallet2::sign(const std::string &data, cryptonote::subaddress_index index) const -{ +// Set up an address signature message hash +// Hash data: domain separator, spend public key, view public key, mode identifier, payload data +static crypto::hash get_message_hash(const std::string &data, const crypto::public_key &spend_key, const crypto::public_key &view_key, const uint8_t mode) +{ + KECCAK_CTX ctx; + keccak_init(&ctx); + keccak_update(&ctx, (const uint8_t*)config::HASH_KEY_MESSAGE_SIGNING, sizeof(config::HASH_KEY_MESSAGE_SIGNING)); // includes NUL + keccak_update(&ctx, (const uint8_t*)&spend_key, sizeof(crypto::public_key)); + keccak_update(&ctx, (const uint8_t*)&view_key, sizeof(crypto::public_key)); + keccak_update(&ctx, (const uint8_t*)&mode, sizeof(uint8_t)); + char len_buf[(sizeof(size_t) * 8 + 6) / 7]; + char *ptr = len_buf; + tools::write_varint(ptr, data.size()); + CHECK_AND_ASSERT_THROW_MES(ptr > len_buf && ptr <= len_buf + sizeof(len_buf), "Length overflow"); + keccak_update(&ctx, (const uint8_t*)len_buf, ptr - len_buf); + keccak_update(&ctx, (const uint8_t*)data.data(), data.size()); crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size(), hash); + keccak_finish(&ctx, (uint8_t*)&hash); + return hash; +} + +// Sign a message with a private key from either the base address or a subaddress +// The signature is also bound to both keys and the signature mode (spend, view) to prevent unintended reuse +std::string wallet2::sign(const std::string &data, message_signature_type_t signature_type, cryptonote::subaddress_index index) const +{ const cryptonote::account_keys &keys = m_account.get_keys(); crypto::signature signature; - crypto::secret_key skey; + crypto::secret_key skey, m; + crypto::secret_key skey_spend, skey_view; crypto::public_key pkey; + crypto::public_key pkey_spend, pkey_view; // to include both in hash + crypto::hash hash; + uint8_t mode; + + // Use the base address if (index.is_zero()) { - skey = keys.m_spend_secret_key; - pkey = keys.m_account_address.m_spend_public_key; + switch (signature_type) + { + case sign_with_spend_key: + skey = keys.m_spend_secret_key; + pkey = keys.m_account_address.m_spend_public_key; + mode = 0; + break; + case sign_with_view_key: + skey = keys.m_view_secret_key; + pkey = keys.m_account_address.m_view_public_key; + mode = 1; + break; + default: CHECK_AND_ASSERT_THROW_MES(false, "Invalid signature type requested"); + } + hash = get_message_hash(data,keys.m_account_address.m_spend_public_key,keys.m_account_address.m_view_public_key,mode); } + // Use a subaddress else { - skey = keys.m_spend_secret_key; - crypto::secret_key m = m_account.get_device().get_subaddress_secret_key(keys.m_view_secret_key, index); - sc_add((unsigned char*)&skey, (unsigned char*)&m, (unsigned char*)&skey); + skey_spend = keys.m_spend_secret_key; + m = m_account.get_device().get_subaddress_secret_key(keys.m_view_secret_key, index); + sc_add((unsigned char*)&skey_spend, (unsigned char*)&m, (unsigned char*)&skey_spend); + secret_key_to_public_key(skey_spend,pkey_spend); + sc_mul((unsigned char*)&skey_view, (unsigned char*)&keys.m_view_secret_key, (unsigned char*)&skey_spend); + secret_key_to_public_key(skey_view,pkey_view); + switch (signature_type) + { + case sign_with_spend_key: + skey = skey_spend; + pkey = pkey_spend; + mode = 0; + break; + case sign_with_view_key: + skey = skey_view; + pkey = pkey_view; + mode = 1; + break; + default: CHECK_AND_ASSERT_THROW_MES(false, "Invalid signature type requested"); + } secret_key_to_public_key(skey, pkey); + hash = get_message_hash(data,pkey_spend,pkey_view,mode); } crypto::generate_signature(hash, pkey, skey, signature); - return std::string("SigV1") + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); + return std::string("SigV2") + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); } -bool wallet2::verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const +tools::wallet2::message_signature_result_t wallet2::verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const { - const size_t header_len = strlen("SigV1"); - if (signature.size() < header_len || signature.substr(0, header_len) != "SigV1") { + static const size_t v1_header_len = strlen("SigV1"); + static const size_t v2_header_len = strlen("SigV2"); + const bool v1 = signature.size() >= v1_header_len && signature.substr(0, v1_header_len) == "SigV1"; + const bool v2 = signature.size() >= v2_header_len && signature.substr(0, v2_header_len) == "SigV2"; + if (!v1 && !v2) + { LOG_PRINT_L0("Signature header check error"); - return false; + return {}; } crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size(), hash); + if (v1) + { + crypto::cn_fast_hash(data.data(), data.size(), hash); + } std::string decoded; - if (!tools::base58::decode(signature.substr(header_len), decoded)) { + if (!tools::base58::decode(signature.substr(v1 ? v1_header_len : v2_header_len), decoded)) { LOG_PRINT_L0("Signature decoding error"); - return false; + return {}; } crypto::signature s; if (sizeof(s) != decoded.size()) { LOG_PRINT_L0("Signature decoding error"); - return false; + return {}; } memcpy(&s, decoded.data(), sizeof(s)); - return crypto::check_signature(hash, address.m_spend_public_key, s); + + // Test each mode and return which mode, if either, succeeded + if (v2) + hash = get_message_hash(data,address.m_spend_public_key,address.m_view_public_key,(uint8_t) 0); + if (crypto::check_signature(hash, address.m_spend_public_key, s)) + return {true, v1 ? 1u : 2u, !v2, sign_with_spend_key }; + + if (v2) + hash = get_message_hash(data,address.m_spend_public_key,address.m_view_public_key,(uint8_t) 1); + if (crypto::check_signature(hash, address.m_view_public_key, s)) + return {true, v1 ? 1u : 2u, !v2, sign_with_view_key }; + + // Both modes failed + return {}; } std::string wallet2::sign_multisig_participant(const std::string& data) const @@ -12728,9 +12968,9 @@ std::string wallet2::export_outputs_to_str(bool all) const PERF_TIMER(export_outputs_to_str); std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - const auto& outputs = export_outputs(all); - ar << outputs; + binary_archive<true> ar(oss); + auto outputs = export_outputs(all); + THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, outputs), error::wallet_internal_error, "Failed to serialize output data"); std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; @@ -12842,23 +13082,39 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) } size_t imported_outputs = 0; + bool loaded = false; try { std::string body(data, headerlen); - std::stringstream iss; - iss << body; std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs; try { - boost::archive::portable_binary_iarchive ar(iss); - ar >> outputs; + std::stringstream iss; + iss << body; + binary_archive<false> ar(iss); + if (::serialization::serialize(ar, outputs)) + if (::serialization::check_stream_state(ar)) + loaded = true; } - catch (...) + catch (...) {} + + if (!loaded && m_load_deprecated_formats) { - iss.str(""); - iss << body; - boost::archive::binary_iarchive ar(iss); - ar >> outputs; + try + { + std::stringstream iss; + iss << body; + boost::archive::portable_binary_iarchive ar(iss); + ar >> outputs; + loaded = true; + } + catch (...) {} + } + + if (!loaded) + { + outputs.first = 0; + outputs.second = {}; } imported_outputs = import_outputs(outputs); @@ -13012,8 +13268,8 @@ cryptonote::blobdata wallet2::export_multisig() } std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << info; + binary_archive<true> ar(oss); + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(ar, info), "Failed to serialize multisig data"); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; std::string header; @@ -13083,10 +13339,26 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) seen.insert(signer); std::string body(data, headerlen); - std::istringstream iss(body); std::vector<tools::wallet2::multisig_info> i; - boost::archive::portable_binary_iarchive ar(iss); - ar >> i; + + bool loaded = false; + try + { + std::istringstream iss(body); + binary_archive<false> ar(iss); + if (::serialization::serialize(ar, i)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } + catch(...) {} + if (!loaded && m_load_deprecated_formats) + { + std::istringstream iss(body); + boost::archive::portable_binary_iarchive ar(iss); + ar >> i; + loaded = true; + } + CHECK_AND_ASSERT_THROW_MES(loaded, "Failed to load output data"); MINFO(boost::format("%u outputs found") % boost::lexical_cast<std::string>(i.size())); info.push_back(std::move(i)); } @@ -13124,7 +13396,7 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) // sort by signer if (!info.empty() && !info.front().empty()) { - std::sort(info.begin(), info.end(), [](const std::vector<tools::wallet2::multisig_info> &i0, const std::vector<tools::wallet2::multisig_info> &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)); }); + std::sort(info.begin(), info.end(), [](const std::vector<tools::wallet2::multisig_info> &i0, const std::vector<tools::wallet2::multisig_info> &i1){ return memcmp(&i0[0].m_signer, &i1[0].m_signer, sizeof(i0[0].m_signer)) < 0; }); } // first pass to determine where to detach the blockchain @@ -13876,8 +14148,9 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i n_outputs = 2; // extra dummy output const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); - size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); + const bool clsag = use_fork_rules(get_clsag_fork(), 0); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); return std::make_pair(size, weight); } //---------------------------------------------------------------------------------------------------- |