diff --git a/nano/secure/CMakeLists.txt b/nano/secure/CMakeLists.txt index a6873caf3b..f80ac9d8b3 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -47,6 +47,8 @@ add_library( network_filter.cpp utility.hpp utility.cpp + vote.hpp + vote.cpp working.hpp) target_link_libraries(secure nano_lib ed25519 crypto_lib Boost::system) diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index 09e37ea965..faf3b5d527 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -430,191 +430,6 @@ nano::block_info::block_info (nano::account const & account_a, nano::amount cons { } -bool nano::vote::operator== (nano::vote const & other_a) const -{ - return timestamp_m == other_a.timestamp_m && hashes == other_a.hashes && account == other_a.account && signature == other_a.signature; -} - -bool nano::vote::operator!= (nano::vote const & other_a) const -{ - return !(*this == other_a); -} - -void nano::vote::serialize_json (boost::property_tree::ptree & tree) const -{ - tree.put ("account", account.to_account ()); - tree.put ("signature", signature.number ()); - tree.put ("sequence", std::to_string (timestamp ())); - tree.put ("timestamp", std::to_string (timestamp ())); - tree.put ("duration", std::to_string (duration_bits ())); - boost::property_tree::ptree blocks_tree; - for (auto const & hash : hashes) - { - boost::property_tree::ptree entry; - entry.put ("", hash.to_string ()); - blocks_tree.push_back (std::make_pair ("", entry)); - } - tree.add_child ("blocks", blocks_tree); -} - -std::string nano::vote::to_json () const -{ - std::stringstream stream; - boost::property_tree::ptree tree; - serialize_json (tree); - boost::property_tree::write_json (stream, tree); - return stream.str (); -} - -/** - * Returns the timestamp of the vote (with the duration bits masked, set to zero) - * If it is a final vote, all the bits including duration bits are returned as they are, all FF - */ -uint64_t nano::vote::timestamp () const -{ - return (timestamp_m == std::numeric_limits::max ()) - ? timestamp_m // final vote - : (timestamp_m & timestamp_mask); -} - -uint8_t nano::vote::duration_bits () const -{ - // Duration field is specified in the 4 low-order bits of the timestamp. - // This makes the timestamp have a minimum granularity of 16ms - // The duration is specified as 2^(duration + 4) giving it a range of 16-524,288ms in power of two increments - auto result = timestamp_m & ~timestamp_mask; - debug_assert (result < 16); - return static_cast (result); -} - -std::chrono::milliseconds nano::vote::duration () const -{ - return std::chrono::milliseconds{ 1u << (duration_bits () + 4) }; -} - -nano::vote::vote (nano::vote const & other_a) : - timestamp_m{ other_a.timestamp_m }, - hashes{ other_a.hashes }, - account (other_a.account), - signature (other_a.signature) -{ -} - -nano::vote::vote (bool & error_a, nano::stream & stream_a) -{ - error_a = deserialize (stream_a); -} - -nano::vote::vote (nano::account const & account_a, nano::raw_key const & prv_a, uint64_t timestamp_a, uint8_t duration, std::vector const & hashes) : - hashes{ hashes }, - timestamp_m{ packed_timestamp (timestamp_a, duration) }, - account (account_a) -{ - signature = nano::sign_message (prv_a, account_a, hash ()); -} - -std::string nano::vote::hashes_string () const -{ - std::string result; - for (auto const & hash : hashes) - { - result += hash.to_string (); - result += ", "; - } - return result; -} - -std::string const nano::vote::hash_prefix = "vote "; - -nano::block_hash nano::vote::hash () const -{ - nano::block_hash result; - blake2b_state hash; - blake2b_init (&hash, sizeof (result.bytes)); - blake2b_update (&hash, hash_prefix.data (), hash_prefix.size ()); - for (auto const & block_hash : hashes) - { - blake2b_update (&hash, block_hash.bytes.data (), sizeof (block_hash.bytes)); - } - union - { - uint64_t qword; - std::array bytes; - }; - qword = timestamp_m; - blake2b_update (&hash, bytes.data (), sizeof (bytes)); - blake2b_final (&hash, result.bytes.data (), sizeof (result.bytes)); - return result; -} - -nano::block_hash nano::vote::full_hash () const -{ - nano::block_hash result; - blake2b_state state; - blake2b_init (&state, sizeof (result.bytes)); - blake2b_update (&state, hash ().bytes.data (), sizeof (hash ().bytes)); - blake2b_update (&state, account.bytes.data (), sizeof (account.bytes.data ())); - blake2b_update (&state, signature.bytes.data (), sizeof (signature.bytes.data ())); - blake2b_final (&state, result.bytes.data (), sizeof (result.bytes)); - return result; -} - -void nano::vote::serialize (nano::stream & stream_a) const -{ - write (stream_a, account); - write (stream_a, signature); - write (stream_a, boost::endian::native_to_little (timestamp_m)); - for (auto const & hash : hashes) - { - write (stream_a, hash); - } -} - -bool nano::vote::deserialize (nano::stream & stream_a) -{ - auto error = false; - try - { - nano::read (stream_a, account.bytes); - nano::read (stream_a, signature.bytes); - nano::read (stream_a, timestamp_m); - - while (stream_a.in_avail () > 0) - { - nano::block_hash block_hash; - nano::read (stream_a, block_hash); - hashes.push_back (block_hash); - } - } - catch (std::runtime_error const &) - { - error = true; - } - return error; -} - -bool nano::vote::validate () const -{ - return nano::validate_message (account, hash (), signature); -} - -uint64_t nano::vote::packed_timestamp (uint64_t timestamp, uint8_t duration) const -{ - debug_assert (duration <= duration_max && "Invalid duration"); - debug_assert ((!(timestamp == timestamp_max) || (duration == duration_max)) && "Invalid final vote"); - return (timestamp & timestamp_mask) | duration; -} - -bool nano::vote::is_final_timestamp (uint64_t timestamp) -{ - return timestamp == std::numeric_limits::max (); -} - -nano::block_hash nano::iterate_vote_blocks_as_hash::operator() (nano::block_hash const & item) const -{ - return item; -} - nano::vote_uniquer::vote_uniquer (nano::block_uniquer & uniquer_a) : uniquer (uniquer_a) { diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index cd8cfe7802..7b8fd35754 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -243,64 +244,6 @@ namespace confirmation_height uint64_t const unbounded_cutoff{ 16384 }; } -using vote_blocks_vec_iter = std::vector::const_iterator; -class iterate_vote_blocks_as_hash final -{ -public: - iterate_vote_blocks_as_hash () = default; - nano::block_hash operator() (nano::block_hash const & item) const; -}; -class vote final -{ -public: - vote () = default; - vote (nano::vote const &); - vote (bool &, nano::stream &); - vote (nano::account const &, nano::raw_key const &, nano::millis_t timestamp, uint8_t duration, std::vector const &); - std::string hashes_string () const; - nano::block_hash hash () const; - nano::block_hash full_hash () const; - bool operator== (nano::vote const &) const; - bool operator!= (nano::vote const &) const; - void serialize (nano::stream &) const; - void serialize_json (boost::property_tree::ptree & tree) const; - /** - * Deserializes a vote from the bytes in `stream' - * Returns true if there was an error - */ - bool deserialize (nano::stream &); - bool validate () const; - boost::transform_iterator begin () const; - boost::transform_iterator end () const; - std::string to_json () const; - uint64_t timestamp () const; - uint8_t duration_bits () const; - std::chrono::milliseconds duration () const; - - static uint64_t constexpr timestamp_mask = { 0xffff'ffff'ffff'fff0ULL }; - static nano::seconds_t constexpr timestamp_max = { 0xffff'ffff'ffff'fff0ULL }; - static uint64_t constexpr timestamp_min = { 0x0000'0000'0000'0010ULL }; - static uint8_t constexpr duration_max = { 0x0fu }; - - /* Check if timestamp represents a final vote */ - static bool is_final_timestamp (uint64_t timestamp); - -private: - // Vote timestamp - uint64_t timestamp_m; - -public: - // The hashes for which this vote directly covers - std::vector hashes; - // Account that's voting - nano::account account; - // Signature of timestamp + block hashes - nano::signature signature; - static std::string const hash_prefix; - -private: - uint64_t packed_timestamp (uint64_t timestamp, uint8_t duration) const; -}; /** * This class serves to find and return unique variants of a vote in order to minimize memory usage */ diff --git a/nano/secure/vote.cpp b/nano/secure/vote.cpp new file mode 100644 index 0000000000..2a7a669334 --- /dev/null +++ b/nano/secure/vote.cpp @@ -0,0 +1,185 @@ +#include +#include + +#include + +nano::vote::vote (bool & error_a, nano::stream & stream_a) +{ + error_a = deserialize (stream_a); +} + +nano::vote::vote (nano::account const & account_a, nano::raw_key const & prv_a, uint64_t timestamp_a, uint8_t duration, std::vector const & hashes) : + hashes{ hashes }, + timestamp_m{ packed_timestamp (timestamp_a, duration) }, + account{ account_a } +{ + signature = nano::sign_message (prv_a, account_a, hash ()); +} + +void nano::vote::serialize (nano::stream & stream_a) const +{ + write (stream_a, account); + write (stream_a, signature); + write (stream_a, boost::endian::native_to_little (timestamp_m)); + for (auto const & hash : hashes) + { + write (stream_a, hash); + } +} + +bool nano::vote::deserialize (nano::stream & stream_a) +{ + auto error = false; + try + { + nano::read (stream_a, account.bytes); + nano::read (stream_a, signature.bytes); + nano::read (stream_a, timestamp_m); + + while (stream_a.in_avail () > 0) + { + nano::block_hash block_hash; + nano::read (stream_a, block_hash); + hashes.push_back (block_hash); + } + } + catch (std::runtime_error const &) + { + error = true; + } + return error; +} + +std::string const nano::vote::hash_prefix = "vote "; + +nano::block_hash nano::vote::hash () const +{ + nano::block_hash result; + blake2b_state hash; + blake2b_init (&hash, sizeof (result.bytes)); + blake2b_update (&hash, hash_prefix.data (), hash_prefix.size ()); + for (auto const & block_hash : hashes) + { + blake2b_update (&hash, block_hash.bytes.data (), sizeof (block_hash.bytes)); + } + union + { + uint64_t qword; + std::array bytes; + }; + qword = timestamp_m; + blake2b_update (&hash, bytes.data (), sizeof (bytes)); + blake2b_final (&hash, result.bytes.data (), sizeof (result.bytes)); + return result; +} + +nano::block_hash nano::vote::full_hash () const +{ + nano::block_hash result; + blake2b_state state; + blake2b_init (&state, sizeof (result.bytes)); + blake2b_update (&state, hash ().bytes.data (), sizeof (hash ().bytes)); + blake2b_update (&state, account.bytes.data (), sizeof (account.bytes.data ())); + blake2b_update (&state, signature.bytes.data (), sizeof (signature.bytes.data ())); + blake2b_final (&state, result.bytes.data (), sizeof (result.bytes)); + return result; +} + +bool nano::vote::validate () const +{ + return nano::validate_message (account, hash (), signature); +} + +bool nano::vote::operator== (nano::vote const & other_a) const +{ + return timestamp_m == other_a.timestamp_m && hashes == other_a.hashes && account == other_a.account && signature == other_a.signature; +} + +bool nano::vote::operator!= (nano::vote const & other_a) const +{ + return !(*this == other_a); +} + +/** + * Returns the timestamp of the vote (with the duration bits masked, set to zero) + * If it is a final vote, all the bits including duration bits are returned as they are, all FF + */ +uint64_t nano::vote::timestamp () const +{ + return (timestamp_m == std::numeric_limits::max ()) + ? timestamp_m // final vote + : (timestamp_m & timestamp_mask); +} + +uint8_t nano::vote::duration_bits () const +{ + // Duration field is specified in the 4 low-order bits of the timestamp. + // This makes the timestamp have a minimum granularity of 16ms + // The duration is specified as 2^(duration + 4) giving it a range of 16-524,288ms in power of two increments + auto result = timestamp_m & ~timestamp_mask; + debug_assert (result < 16); + return static_cast (result); +} + +std::chrono::milliseconds nano::vote::duration () const +{ + return std::chrono::milliseconds{ 1u << (duration_bits () + 4) }; +} + +void nano::vote::serialize_json (boost::property_tree::ptree & tree) const +{ + tree.put ("account", account.to_account ()); + tree.put ("signature", signature.number ()); + tree.put ("sequence", std::to_string (timestamp ())); + tree.put ("timestamp", std::to_string (timestamp ())); + tree.put ("duration", std::to_string (duration_bits ())); + boost::property_tree::ptree blocks_tree; + for (auto const & hash : hashes) + { + boost::property_tree::ptree entry; + entry.put ("", hash.to_string ()); + blocks_tree.push_back (std::make_pair ("", entry)); + } + tree.add_child ("blocks", blocks_tree); +} + +std::string nano::vote::to_json () const +{ + std::stringstream stream; + boost::property_tree::ptree tree; + serialize_json (tree); + boost::property_tree::write_json (stream, tree); + return stream.str (); +} + +std::string nano::vote::hashes_string () const +{ + std::string result; + for (auto const & hash : hashes) + { + result += hash.to_string (); + result += ", "; + } + return result; +} + +uint64_t nano::vote::packed_timestamp (uint64_t timestamp, uint8_t duration) +{ + debug_assert (duration <= duration_max && "Invalid duration"); + debug_assert ((!(timestamp == timestamp_max) || (duration == duration_max)) && "Invalid final vote"); + return (timestamp & timestamp_mask) | duration; +} + +bool nano::vote::is_final_timestamp (uint64_t timestamp) +{ + return timestamp == std::numeric_limits::max (); +} + +/* + * iterate_vote_blocks_as_hash + */ + +nano::block_hash nano::iterate_vote_blocks_as_hash::operator() (nano::block_hash const & item) const +{ + return item; +} diff --git a/nano/secure/vote.hpp b/nano/secure/vote.hpp new file mode 100644 index 0000000000..b20fcb3671 --- /dev/null +++ b/nano/secure/vote.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace nano +{ +using vote_blocks_vec_iter = std::vector::const_iterator; +class iterate_vote_blocks_as_hash final +{ +public: + iterate_vote_blocks_as_hash () = default; + nano::block_hash operator() (nano::block_hash const & item) const; +}; + +class vote final +{ +public: + vote () = default; + vote (nano::vote const &) = default; + vote (bool & error, nano::stream &); + vote (nano::account const &, nano::raw_key const &, nano::millis_t timestamp, uint8_t duration, std::vector const & hashes); + + void serialize (nano::stream &) const; + /** + * Deserializes a vote from the bytes in `stream' + * @returns true if there was an error + */ + bool deserialize (nano::stream &); + + nano::block_hash hash () const; + nano::block_hash full_hash () const; + bool validate () const; + + bool operator== (nano::vote const &) const; + bool operator!= (nano::vote const &) const; + + boost::transform_iterator begin () const; + boost::transform_iterator end () const; + + void serialize_json (boost::property_tree::ptree & tree) const; + std::string to_json () const; + std::string hashes_string () const; + + uint64_t timestamp () const; + uint8_t duration_bits () const; + std::chrono::milliseconds duration () const; + + static uint64_t constexpr timestamp_mask = { 0xffff'ffff'ffff'fff0ULL }; + static nano::seconds_t constexpr timestamp_max = { 0xffff'ffff'ffff'fff0ULL }; + static uint64_t constexpr timestamp_min = { 0x0000'0000'0000'0010ULL }; + static uint8_t constexpr duration_max = { 0x0fu }; + + /* Check if timestamp represents a final vote */ + static bool is_final_timestamp (uint64_t timestamp); + +private: + static std::string const hash_prefix; + + static uint64_t packed_timestamp (uint64_t timestamp, uint8_t duration); + +public: // Payload + // The hashes for which this vote directly covers + std::vector hashes; + // Account that's voting + nano::account account{ 0 }; + // Signature of timestamp + block hashes + nano::signature signature{ 0 }; + +private: // Payload + // Vote timestamp + uint64_t timestamp_m{ 0 }; +}; +} \ No newline at end of file