-
Notifications
You must be signed in to change notification settings - Fork 15
allow hash()
to take multiple arguments similar to pack()
#1526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#pragma once | ||
|
||
#include <tuple> | ||
#include <type_traits> | ||
#include <cstdint> | ||
|
||
namespace fc { | ||
|
||
template<typename Arg0> | ||
concept FirstArgDecaysToCharPtr = | ||
std::is_same_v<std::decay_t<Arg0>, char*> || | ||
std::is_same_v<std::decay_t<Arg0>, const char*>; | ||
|
||
template<typename Arg1> | ||
concept SecondArgIsConvertibleToUint32 = | ||
std::is_convertible_v<std::decay_t<Arg1>, uint32_t>; | ||
|
||
/* Used on template<typename... T> hash_type hash(const T&... t); to prevent calls such as | ||
* std::vector<char> foo; | ||
* hash(foo.data(), foo.size()) | ||
* calling the variadic hash instead of the existing hash(const char* d, uint32_t dlen) | ||
*/ | ||
template<typename... T> | ||
concept NotTwoArgsCharUint32 = | ||
sizeof...(T) != 2 || | ||
!FirstArgDecaysToCharPtr<std::tuple_element_t<0, std::tuple<T...>>> || | ||
!SecondArgIsConvertibleToUint32<std::tuple_element_t<1, std::tuple<T...>>>; | ||
|
||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -4,13 +4,14 @@ | |||||||
#include <fc/fwd.hpp> | ||||||||
#include <fc/string.hpp> | ||||||||
#include <fc/platform_independence.hpp> | ||||||||
#include <fc/io/raw_fwd.hpp> | ||||||||
#include <fc/crypto/hash_concepts.hpp> | ||||||||
#include <fc/io/raw.hpp> | ||||||||
#include <boost/functional/hash.hpp> | ||||||||
|
||||||||
namespace fc | ||||||||
{ | ||||||||
|
||||||||
class sha256 | ||||||||
class sha256 | ||||||||
{ | ||||||||
public: | ||||||||
sha256(); | ||||||||
|
@@ -36,11 +37,12 @@ class sha256 | |||||||
static sha256 hash( const std::string& ); | ||||||||
static sha256 hash( const sha256& ); | ||||||||
|
||||||||
template<typename T> | ||||||||
static sha256 hash( const T& t ) | ||||||||
template<typename... T> | ||||||||
requires NotTwoArgsCharUint32<T...> | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The various hash types have some existing overloads of spring/libraries/libfc/include/fc/crypto/sha256.hpp Lines 35 to 37 in ac0abff
This is pretty gnarly on its own because the std::string overload doesn't follow the same rules as if sending a std::string through the encoder . i.e.
sha256 a = sha256::hash(std::string("banana"));
sha256::encoded enc;
fc::raw::pack(enc, std::string("banana"));
sha256 b = enc.result();
// a != b but the real problem is the std::vector<char> foo;
sha256::hash(foo.data(), foo.size()) I need to ensure that calls like |
||||||||
static sha256 hash( const T&... t ) | ||||||||
{ | ||||||||
sha256::encoder e; | ||||||||
fc::raw::pack(e,t); | ||||||||
fc::raw::pack(e,t...); | ||||||||
return e.result(); | ||||||||
} | ||||||||
|
||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
#include <fc/fwd.hpp> | ||
#include <fc/string.hpp> | ||
#include <fc/platform_independence.hpp> | ||
#include <fc/io/raw_fwd.hpp> | ||
#include <fc/io/raw.hpp> | ||
#include <boost/functional/hash.hpp> | ||
|
||
namespace fc | ||
|
@@ -32,12 +32,23 @@ class sha3 | |
static sha3 hash(const std::string& s, bool is_nist=true) { return hash(s.c_str(), s.size(), is_nist); } | ||
static sha3 hash(const sha3& s, bool is_nist=true) { return hash(s.data(), sizeof(s._hash), is_nist); } | ||
|
||
template <typename T> | ||
static sha3 hash(const T &t, bool is_nist=true) | ||
struct keccak {}; | ||
struct nist {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There didn't appear to be any existing usages of
|
||
|
||
template<typename... T> | ||
static sha3 hash( keccak, const T&... t ) | ||
{ | ||
sha3::encoder e; | ||
fc::raw::pack(e,t...); | ||
return e.result(false); | ||
} | ||
|
||
template<typename... T> | ||
static sha3 hash( nist, const T&... t ) | ||
{ | ||
sha3::encoder e; | ||
fc::raw::pack(e, t); | ||
return e.result(is_nist); | ||
fc::raw::pack(e,t...); | ||
return e.result(true); | ||
} | ||
|
||
class encoder | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -176,13 +176,15 @@ namespace fc { | |
} FC_RETHROW_EXCEPTIONS( warn, "fc::array<${type},${length}>", ("type",fc::get_typename<T>::name())("length",N) ) } | ||
|
||
template<typename Stream, typename T, size_t N> | ||
requires (!std::is_same_v<std::remove_cv_t<T>, char>) | ||
inline void pack( Stream& s, T (&v)[N]) { | ||
fc::raw::pack( s, unsigned_int((uint32_t)N) ); | ||
for (uint64_t i = 0; i < N; ++i) | ||
fc::raw::pack(s, v[i]); | ||
} | ||
|
||
template<typename Stream, typename T, size_t N> | ||
requires (!std::is_same_v<std::remove_cv_t<T>, char>) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When changing from |
||
inline void unpack( Stream& s, T (&v)[N]) | ||
{ try { | ||
unsigned_int size; fc::raw::unpack( s, size ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I think we should rename these to
hash_str
and remove the complicated template concepts.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe safest to rename the variadic one to
hash_values
so it is clear which one is in-use.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be a viable option; I was reluctant to make changes across existing code but I can try and see just how pervasive those changes would be. (many tests did fail without the complicated concepts though it's possible only due to a small number root causes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, you could just add:
to support the case a
hash
called with two or more parameters. I believe it would require no change to existing code or new concepts.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that solves the problem though. Need std::vector .data() and .size() to go through the 'old' overload. e.g.
when running this get
CALLED: Variadic hash
but we needCALLED: hash(const char*, uint32_t)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly the
std::is_convertible_v<std::decay_t<T>, size_t>
is too loose, since even anfc::unsigned_int
will match it (due to it havingoperator uint32_t()
), so you can end up with a nasty surprise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a valid concern for the
CStyleStringPtr
version.In my comment I was more worried about the variadic hash version (
hash( const T&... t )
) catching unexpected calls. One way to check that it doesn't happen would be to add anassert(0)
in its implementation, and run the ci/cd on code that is not yet using the variadic version. If theasserton
build passes, it will be a good indication that existing uses ofhash
didn't migrate to using the variadic version.Upon reflection, I think the pattern of
hash
accepting achar *
+length
is not a good one, it probably would have been better to always have a single parameter to specify an object to be hashed, whether it ischar*
( expected to be null-terminated)std::string
orstd::string_view
vector<T>
where T is a hashable type (or maybe any iterable container where thevalue_type
is hashable)std::span<T>
where T is a hashable typewhich would avoid the ambiguity of a two argument
hash
call specifying one or two values to be hashed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can all be avoided by renaming the existing string ones to hash_chars()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does increasingly feel like renaming the old overloads is the best option, provided there is not too much fallout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the number of calls to the non-templated hash() was substantially more than may have guessed. I prepared a commit where I replace all them to (I hope; didn't audit yet) 1:1 equivalent of new
hash_raw()
, c893000But a substantial number of these don't need to be 1:1 exact as they were before (like in the benchmark, or many/most of tests), so I guess I can build on top of the commit a new commit that migrates some back to plain
hash()
to limit the number of callshash_raw()