RPC "sign" (hash, block) (#1241)

* RPC sign

Signing hash or block

* Typo

* Add test for RPC sign

* block_impl

* Add required header

* Split sign_hash & sign_block

* Headers order

* Allow blocks w/o signature & hash in RPC block_hash

* Prepare for master merge

* Test for RPC sign

* Formatting

* system.io_ctx

* Update test_response

* Unified RPC "sign"

* Config option enable_sign_hash

* Fix

* Formatting

* Fix rpc_config

* explicitly set signature_work_required value for block_impl ()

For better code readability

* Fix transactions
This commit is contained in:
Sergey Kroshnin 2019-01-28 18:18:05 +03:00 committed by GitHub
commit a2fb99e189
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 288 additions and 131 deletions

View file

@ -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<std::string> ("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<std::string> ("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<std::string> ("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 ());
}

View file

@ -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";
}

View file

@ -99,6 +99,7 @@ enum class error_rpc
payment_account_balance,
payment_unable_create_account,
rpc_control_disabled,
sign_hash_disabled,
source_not_found
};

View file

@ -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<uint64_t> ("frontier_request_limit", frontier_request_limit);
json.get_optional<uint64_t> ("chain_request_limit", chain_request_limit);
json.get_optional<uint8_t> ("max_json_depth", max_json_depth);
json.get_optional<bool> ("enable_sign_hash", enable_sign_hash);
return json.get_error ();
}
@ -213,6 +206,37 @@ std::shared_ptr<nano::wallet> nano::rpc_handler::wallet_impl ()
return nullptr;
}
bool nano::rpc_handler::wallet_locked_impl (nano::transaction const & transaction_a, std::shared_ptr<nano::wallet> 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<nano::wallet> 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::block> nano::rpc_handler::block_impl (bool signature_work_required)
{
std::shared_ptr<nano::block> result;
if (!ec)
{
std::string block_text (request.get<std::string> ("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<std::string> ("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<std::string> ("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<nano::block> 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<nano::block> 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<nano::block> 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<std::string> hash_text (request.get_optional<std::string> ("hash"));
if (hash_text.is_initialized ())
{
hash = hash_impl ();
}
// Retrieving block
std::shared_ptr<nano::block> block;
boost::optional<std::string> block_text (request.get_optional<std::string> ("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<std::string> key_text (request.get_optional<std::string> ("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<std::string> account_text (request.get_optional<std::string> ("account"));
boost::optional<std::string> wallet_text (request.get_optional<std::string> ("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 ();

View file

@ -5,8 +5,10 @@
#include <boost/beast.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <nano/lib/blocks.hpp>
#include <nano/lib/errors.hpp>
#include <nano/lib/jsonconfig.hpp>
#include <nano/secure/blockstore.hpp>
#include <nano/secure/utility.hpp>
#include <unordered_map>
@ -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<nano::wallet> wallet_impl ();
bool wallet_locked_impl (nano::transaction const &, std::shared_ptr<nano::wallet>);
bool wallet_account_impl (nano::transaction const &, std::shared_ptr<nano::wallet>, nano::account const &);
nano::account account_impl (std::string = "");
nano::amount amount_impl ();
std::shared_ptr<nano::block> block_impl (bool = true);
nano::block_hash hash_impl (std::string = "hash");
nano::amount threshold_optional_impl ();
uint64_t work_optional_impl ();