diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index 50705196..a860a740 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -4214,3 +4214,70 @@ TEST (rpc, wallet_history) ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[3])); ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[3])); } + +TEST (rpc, sign_hash) +{ + nano::system system (24000, 1); + nano::keypair key; + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "sign"); + request.put ("hash", send.hash ().to_string ()); + request.put ("key", key.prv.data.to_string ()); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::sign_hash_disabled); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + rpc.config.enable_sign_hash = true; + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response2.status); + nano::signature signature; + std::string signature_text (response2.json.get ("signature")); + ASSERT_FALSE (signature.decode_hex (signature_text)); + ASSERT_FALSE (nano::validate_message (key.pub, send.hash (), signature)); +} + +TEST (rpc, sign_block) +{ + nano::system system (24000, 1); + nano::keypair key; + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "sign"); + system.wallet (0)->insert_adhoc (key.prv); + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("account", key.pub.to_account ()); + std::string json; + send.serialize_json (json); + request.put ("block", json); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + auto contents (response.json.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (contents); + boost::property_tree::read_json (block_stream, block_l); + auto block (nano::deserialize_block_json (block_l)); + ASSERT_FALSE (nano::validate_message (key.pub, send.hash (), block->block_signature ())); + ASSERT_NE (block->block_signature (), send.block_signature ()); + ASSERT_EQ (block->hash (), send.hash ()); +} diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 86058b69..920a7c73 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -166,6 +166,8 @@ std::string nano::error_rpc_messages::message (int ev) const return "Unable to create transaction account"; case nano::error_rpc::rpc_control_disabled: return "RPC control is disabled"; + case nano::error_rpc::sign_hash_disabled: + return "Signing by block hash is disabled"; case nano::error_rpc::source_not_found: return "Source not found"; } diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 632f4fc7..fe0df74c 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -99,6 +99,7 @@ enum class error_rpc payment_account_balance, payment_unable_create_account, rpc_control_disabled, + sign_hash_disabled, source_not_found }; diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index 0f0ad624..3e997d38 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -39,23 +39,14 @@ nano::error nano::rpc_secure_config::deserialize_json (nano::jsonconfig & json) return json.get_error (); } -nano::rpc_config::rpc_config () : -address (boost::asio::ip::address_v6::loopback ()), -port (nano::rpc::rpc_port), -enable_control (false), -frontier_request_limit (16384), -chain_request_limit (16384), -max_json_depth (20) -{ -} - nano::rpc_config::rpc_config (bool enable_control_a) : address (boost::asio::ip::address_v6::loopback ()), port (nano::rpc::rpc_port), enable_control (enable_control_a), frontier_request_limit (16384), chain_request_limit (16384), -max_json_depth (20) +max_json_depth (20), +enable_sign_hash (false) { } @@ -67,6 +58,7 @@ nano::error nano::rpc_config::serialize_json (nano::jsonconfig & json) const json.put ("frontier_request_limit", frontier_request_limit); json.put ("chain_request_limit", chain_request_limit); json.put ("max_json_depth", max_json_depth); + json.put ("enable_sign_hash", enable_sign_hash); return json.get_error (); } @@ -84,6 +76,7 @@ nano::error nano::rpc_config::deserialize_json (nano::jsonconfig & json) json.get_optional ("frontier_request_limit", frontier_request_limit); json.get_optional ("chain_request_limit", chain_request_limit); json.get_optional ("max_json_depth", max_json_depth); + json.get_optional ("enable_sign_hash", enable_sign_hash); return json.get_error (); } @@ -213,6 +206,37 @@ std::shared_ptr nano::rpc_handler::wallet_impl () return nullptr; } +bool nano::rpc_handler::wallet_locked_impl (nano::transaction const & transaction_a, std::shared_ptr wallet_a) +{ + bool result (false); + if (!ec) + { + if (!wallet_a->store.valid_password (transaction_a)) + { + ec = nano::error_common::wallet_locked; + result = true; + } + } + return result; +} + +bool nano::rpc_handler::wallet_account_impl (nano::transaction const & transaction_a, std::shared_ptr wallet_a, nano::account const & account_a) +{ + bool result (false); + if (!ec) + { + if (wallet_a->store.find (transaction_a, account_a) != wallet_a->store.end ()) + { + result = true; + } + else + { + ec = nano::error_common::account_not_found_wallet; + } + } + return result; +} + nano::account nano::rpc_handler::account_impl (std::string account_text) { nano::account result (0); @@ -244,6 +268,29 @@ nano::amount nano::rpc_handler::amount_impl () return result; } +std::shared_ptr nano::rpc_handler::block_impl (bool signature_work_required) +{ + std::shared_ptr result; + if (!ec) + { + std::string block_text (request.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (block_text); + boost::property_tree::read_json (block_stream, block_l); + if (!signature_work_required) + { + block_l.put ("signature", "0"); + block_l.put ("work", "0"); + } + result = nano::deserialize_block_json (block_l); + if (result == nullptr) + { + ec = nano::error_blocks::invalid_block; + } + } + return result; +} + nano::block_hash nano::rpc_handler::hash_impl (std::string search_text) { nano::block_hash result (0); @@ -575,21 +622,12 @@ void nano::rpc_handler::account_remove () if (!ec) { auto transaction (node.wallets.tx_begin_write ()); - if (wallet->store.valid_password (transaction)) + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) { - if (wallet->store.find (transaction, account) != wallet->store.end ()) - { - wallet->store.erase (transaction, account); - response_l.put ("removed", "1"); - } - else - { - ec = nano::error_common::account_not_found_wallet; - } - } - else - { - ec = nano::error_common::wallet_locked; + wallet->store.erase (transaction, account); + response_l.put ("removed", "1"); } } response_errors (); @@ -630,11 +668,13 @@ void nano::rpc_handler::account_representative_set () auto work (work_optional_impl ()); if (!ec && work) { - auto transaction (node.store.tx_begin_write ()); - if (wallet->store.valid_password (transaction)) + auto transaction (node.wallets.tx_begin_write ()); + wallet_locked_impl (transaction, wallet); + if (!ec) { nano::account_info info; - if (!node.store.account_get (transaction, account, info)) + auto block_transaction (node.store.tx_begin_read ()); + if (!node.store.account_get (block_transaction, account, info)) { if (nano::work_validate (info.head, work)) { @@ -646,10 +686,6 @@ void nano::rpc_handler::account_representative_set () ec = nano::error_common::account_not_found; } } - else - { - ec = nano::error_common::wallet_locked; - } } if (!ec) { @@ -1088,22 +1124,13 @@ void nano::rpc_handler::block_create () { auto transaction (node.wallets.tx_begin_read ()); auto block_transaction (node.store.tx_begin_read ()); - if (existing->second->store.valid_password (transaction)) + wallet_locked_impl (transaction, existing->second); + wallet_account_impl (transaction, existing->second, account); + if (!ec) { - if (existing->second->store.find (transaction, account) != existing->second->store.end ()) - { - existing->second->store.fetch (transaction, account, prv); - previous = node.ledger.latest (block_transaction, account); - balance = node.ledger.account_balance (block_transaction, account); - } - else - { - ec = nano::error_common::account_not_found_wallet; - } - } - else - { - ec = nano::error_common::wallet_locked; + existing->second->store.fetch (transaction, account, prv); + previous = node.ledger.latest (block_transaction, account); + balance = node.ledger.account_balance (block_transaction, account); } } else @@ -1296,21 +1323,11 @@ void nano::rpc_handler::block_create () void nano::rpc_handler::block_hash () { - std::string block_text (request.get ("block")); - boost::property_tree::ptree block_l; - std::stringstream block_stream (block_text); - boost::property_tree::read_json (block_stream, block_l); - block_l.put ("signature", "0"); - block_l.put ("work", "0"); - auto block (nano::deserialize_block_json (block_l)); - if (block != nullptr) + auto block (block_impl (false)); + if (!ec) { response_l.put ("hash", block->hash ().to_string ()); } - else - { - ec = nano::error_blocks::invalid_block; - } response_errors (); } @@ -2378,8 +2395,8 @@ void nano::rpc_handler::payment_end () { auto transaction (node.wallets.tx_begin_read ()); auto block_transaction (node.store.tx_begin_read ()); - auto existing (wallet->store.find (transaction, account)); - if (existing != wallet->store.end ()) + wallet_account_impl (transaction, wallet, account); + if (!ec) { if (node.ledger.account_balance (block_transaction, account).is_zero ()) { @@ -2391,10 +2408,6 @@ void nano::rpc_handler::payment_end () ec = nano::error_rpc::payment_account_balance; } } - else - { - ec = nano::error_common::account_not_found_wallet; - } } response_errors (); } @@ -2431,12 +2444,8 @@ void nano::rpc_handler::payment_wait () void nano::rpc_handler::process () { - std::string block_text (request.get ("block")); - boost::property_tree::ptree block_l; - std::stringstream block_stream (block_text); - boost::property_tree::read_json (block_stream, block_l); - std::shared_ptr block (nano::deserialize_block_json (block_l)); - if (block != nullptr) + auto block (block_impl (true)); + if (!ec) { if (!nano::work_validate (*block)) { @@ -2522,10 +2531,6 @@ void nano::rpc_handler::process () ec = nano::error_blocks::work_low; } } - else - { - ec = nano::error_blocks::invalid_block; - } response_errors (); } @@ -2537,70 +2542,62 @@ void nano::rpc_handler::receive () auto hash (hash_impl ("block")); if (!ec) { - auto transaction (node.store.tx_begin_read ()); - if (wallet->store.valid_password (transaction)) + auto transaction (node.wallets.tx_begin_read ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) { - if (wallet->store.find (transaction, account) != wallet->store.end ()) + auto block_transaction (node.store.tx_begin_read ()); + auto block (node.store.block_get (block_transaction, hash)); + if (block != nullptr) { - auto block (node.store.block_get (transaction, hash)); - if (block != nullptr) + if (node.store.pending_exists (block_transaction, nano::pending_key (account, hash))) { - if (node.store.pending_exists (transaction, nano::pending_key (account, hash))) + auto work (work_optional_impl ()); + if (!ec && work) { - auto work (work_optional_impl ()); - if (!ec && work) + nano::account_info info; + nano::uint256_union head; + if (!node.store.account_get (block_transaction, account, info)) { - nano::account_info info; - nano::uint256_union head; - if (!node.store.account_get (transaction, account, info)) - { - head = info.head; - } - else - { - head = account; - } - if (nano::work_validate (head, work)) - { - ec = nano::error_common::invalid_work; - } + head = info.head; } - if (!ec) + else { - bool generate_work (work == 0); // Disable work generation if "work" option is provided - auto response_a (response); - wallet->receive_async (std::move (block), account, nano::genesis_amount, [response_a](std::shared_ptr block_a) { - nano::uint256_union hash_a (0); - if (block_a != nullptr) - { - hash_a = block_a->hash (); - } - boost::property_tree::ptree response_l; - response_l.put ("block", hash_a.to_string ()); - response_a (response_l); - }, - work, generate_work); + head = account; + } + if (nano::work_validate (head, work)) + { + ec = nano::error_common::invalid_work; } } - else + if (!ec) { - ec = nano::error_process::unreceivable; + bool generate_work (work == 0); // Disable work generation if "work" option is provided + auto response_a (response); + wallet->receive_async (std::move (block), account, nano::genesis_amount, [response_a](std::shared_ptr block_a) { + nano::uint256_union hash_a (0); + if (block_a != nullptr) + { + hash_a = block_a->hash (); + } + boost::property_tree::ptree response_l; + response_l.put ("block", hash_a.to_string ()); + response_a (response_l); + }, + work, generate_work); } } else { - ec = nano::error_blocks::not_found; + ec = nano::error_process::unreceivable; } } else { - ec = nano::error_common::account_not_found_wallet; + ec = nano::error_blocks::not_found; } } - else - { - ec = nano::error_common::wallet_locked; - } } // Because of receive_async if (ec) @@ -2967,6 +2964,92 @@ void nano::rpc_handler::send () } } +void nano::rpc_handler::sign () +{ + // Retrieving hash + nano::block_hash hash (0); + boost::optional hash_text (request.get_optional ("hash")); + if (hash_text.is_initialized ()) + { + hash = hash_impl (); + } + // Retrieving block + std::shared_ptr block; + boost::optional block_text (request.get_optional ("block")); + if (!ec && block_text.is_initialized ()) + { + block = block_impl (true); + if (block != nullptr) + { + hash = block->hash (); + } + } + // Hash or block are not initialized + if (!ec && hash.is_zero ()) + { + ec = nano::error_blocks::invalid_block; + } + // Hash is initialized without config permission + else if (!ec && !hash.is_zero () && block == nullptr && !rpc.config.enable_sign_hash) + { + ec = nano::error_rpc::sign_hash_disabled; + } + if (!ec) + { + nano::raw_key prv; + prv.data.clear (); + // Retrieving private key from request + boost::optional key_text (request.get_optional ("key")); + if (key_text.is_initialized ()) + { + if (prv.data.decode_hex (key_text.get ())) + { + ec = nano::error_common::bad_private_key; + } + } + else + { + // Retrieving private key from wallet + boost::optional account_text (request.get_optional ("account")); + boost::optional wallet_text (request.get_optional ("wallet")); + if (wallet_text.is_initialized () && account_text.is_initialized ()) + { + auto account (account_impl ()); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + wallet->store.fetch (transaction, account, prv); + } + } + } + } + // Signing + if (prv.data != 0) + { + nano::public_key pub (nano::pub_key (prv.data)); + nano::signature signature (nano::sign_message (prv, pub, hash)); + response_l.put ("signature", signature.to_string ()); + if (block != nullptr) + { + block->signature_set (signature); + std::string contents; + block->serialize_json (contents); + response_l.put ("block", contents); + } + } + else + { + ec = nano::error_rpc::block_create_key_required; + } + } + response_errors (); +} + void nano::rpc_handler::stats () { auto sink = node.stats.log_sink_json (); @@ -3778,17 +3861,14 @@ void nano::rpc_handler::work_get () if (!ec) { auto transaction (node.wallets.tx_begin_read ()); - if (wallet->store.find (transaction, account) != wallet->store.end ()) + wallet_account_impl (transaction, wallet, account); + if (!ec) { uint64_t work (0); auto error_work (wallet->store.work_get (transaction, account, work)); (void)error_work; response_l.put ("work", nano::to_string_hex (work)); } - else - { - ec = nano::error_common::account_not_found_wallet; - } } response_errors (); } @@ -3802,15 +3882,12 @@ void nano::rpc_handler::work_set () if (!ec) { auto transaction (node.wallets.tx_begin_write ()); - if (wallet->store.find (transaction, account) != wallet->store.end ()) + wallet_account_impl (transaction, wallet, account); + if (!ec) { wallet->store.work_put (transaction, account, work); response_l.put ("success", ""); } - else - { - ec = nano::error_common::account_not_found_wallet; - } } response_errors (); } @@ -4330,6 +4407,10 @@ void nano::rpc_handler::process_request () { send (); } + else if (action == "sign") + { + sign (); + } else if (action == "stats") { stats (); diff --git a/nano/node/rpc.hpp b/nano/node/rpc.hpp index 90c58a85..fe8566ee 100644 --- a/nano/node/rpc.hpp +++ b/nano/node/rpc.hpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -40,8 +42,7 @@ public: class rpc_config { public: - rpc_config (); - rpc_config (bool); + rpc_config (bool = false); nano::error serialize_json (nano::jsonconfig &) const; nano::error deserialize_json (nano::jsonconfig &); boost::asio::ip::address_v6 address; @@ -51,6 +52,7 @@ public: uint64_t chain_request_limit; rpc_secure_config secure; uint8_t max_json_depth; + bool enable_sign_hash; }; enum class payment_status { @@ -189,6 +191,7 @@ public: void search_pending (); void search_pending_all (); void send (); + void sign (); void stats (); void stop (); void unchecked (); @@ -235,8 +238,11 @@ public: std::error_code ec; boost::property_tree::ptree response_l; std::shared_ptr wallet_impl (); + bool wallet_locked_impl (nano::transaction const &, std::shared_ptr); + bool wallet_account_impl (nano::transaction const &, std::shared_ptr, nano::account const &); nano::account account_impl (std::string = ""); nano::amount amount_impl (); + std::shared_ptr block_impl (bool = true); nano::block_hash hash_impl (std::string = "hash"); nano::amount threshold_optional_impl (); uint64_t work_optional_impl ();