This repository has been archived by the owner on Aug 2, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I want two things from AuthorityChecker that it wasn't doing yet: 1. I want it to track which of the provided keys were used, so I can reject transactions which bear more signatures than are necessary 2. To sign a transaction with no unnecessary keys, I want it to support taking a list of available public keys and an authority, then telling me which of those keys I should use to fully satisfy the authority, without having any unnecessary keys As an added bonus, having both of these operations handled by AuthorityChecker means that determining the set of keys needed to sign a transaction, and determining whether a transaction is properly signed, use the same code. :D
- Loading branch information
1 parent
1f4753d
commit 16306e9
Showing
5 changed files
with
252 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
libraries/chain/include/eos/chain/authority_checker.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
#pragma once | ||
|
||
#include <eos/chain/types.hpp> | ||
#include <eos/types/generated.hpp> | ||
|
||
#include <eos/utilities/parallel_markers.hpp> | ||
|
||
#include <fc/scoped_exit.hpp> | ||
|
||
#include <boost/range/algorithm/find.hpp> | ||
#include <boost/algorithm/cxx11/all_of.hpp> | ||
|
||
namespace eos { namespace chain { | ||
|
||
namespace detail { | ||
using MetaPermission = static_variant<types::KeyPermissionWeight, types::AccountPermissionWeight>; | ||
|
||
struct GetWeightVisitor { | ||
using result_type = UInt32; | ||
|
||
template<typename Permission> | ||
UInt32 operator()(const Permission& permission) { return permission.weight; } | ||
}; | ||
|
||
// Orders permissions descending by weight, and breaks ties with Key permissions being less than Account permissions | ||
struct MetaPermissionComparator { | ||
bool operator()(const MetaPermission& a, const MetaPermission& b) const { | ||
GetWeightVisitor scale; | ||
if (a.visit(scale) > b.visit(scale)) return true; | ||
return a.contains<types::KeyPermissionWeight>() && !b.contains<types::KeyPermissionWeight>(); | ||
} | ||
}; | ||
|
||
using MetaPermissionSet = boost::container::flat_multiset<MetaPermission, MetaPermissionComparator>; | ||
} | ||
|
||
/** | ||
* @brief This class determines whether a set of signing keys are sufficient to satisfy an authority or not | ||
* | ||
* To determine whether an authority is satisfied or not, we first determine which keys have approved of a message, and | ||
* then determine whether that list of keys is sufficient to satisfy the authority. This class takes a list of keys and | ||
* provides the @ref satisfied method to determine whether that list of keys satisfies a provided authority. | ||
* | ||
* @tparam F A callable which takes a single argument of type @ref AccountPermission and returns the corresponding | ||
* authority | ||
*/ | ||
template<typename F> | ||
class AuthorityChecker { | ||
F PermissionToAuthority; | ||
vector<public_key_type> signingKeys; | ||
vector<bool> usedKeys; | ||
|
||
struct WeightTallyVisitor { | ||
using result_type = UInt32; | ||
|
||
AuthorityChecker& checker; | ||
UInt32 totalWeight = 0; | ||
|
||
WeightTallyVisitor(AuthorityChecker& checker) | ||
: checker(checker) {} | ||
|
||
UInt32 operator()(const types::KeyPermissionWeight& permission) { | ||
auto itr = boost::find(checker.signingKeys, permission.key); | ||
if (itr != checker.signingKeys.end()) { | ||
checker.usedKeys[itr - checker.signingKeys.begin()] = true; | ||
totalWeight += permission.weight; | ||
} | ||
return totalWeight; | ||
} | ||
UInt32 operator()(const types::AccountPermissionWeight& permission) { | ||
//TODO: Recursion limit? Yes: implement as producer-configurable parameter | ||
if (checker.satisfied(permission.permission)) | ||
totalWeight += permission.weight; | ||
return totalWeight; | ||
} | ||
}; | ||
|
||
public: | ||
AuthorityChecker(F PermissionToAuthority, const flat_set<public_key_type>& signingKeys) | ||
: PermissionToAuthority(PermissionToAuthority), | ||
signingKeys(signingKeys.begin(), signingKeys.end()), | ||
usedKeys(signingKeys.size(), false) | ||
{} | ||
|
||
bool satisfied(const types::AccountPermission& permission) { | ||
return satisfied(PermissionToAuthority(permission)); | ||
} | ||
template<typename AuthorityType> | ||
bool satisfied(const AuthorityType& authority) { | ||
// Save the current used keys; if we do not satisfy this authority, the newly used keys aren't actually used | ||
auto KeyReverter = fc::make_scoped_exit([this, keys = usedKeys] () mutable { | ||
usedKeys = keys; | ||
}); | ||
|
||
// Sort key permissions and account permissions together into a single set of MetaPermissions | ||
detail::MetaPermissionSet permissions; | ||
permissions.insert(authority.keys.begin(), authority.keys.end()); | ||
permissions.insert(authority.accounts.begin(), authority.accounts.end()); | ||
|
||
// Check all permissions, from highest weight to lowest, seeing if signingKeys satisfies them or not | ||
WeightTallyVisitor visitor(*this); | ||
for (const auto& permission : permissions) | ||
// If we've got enough weight, to satisfy the authority, return! | ||
if (permission.visit(visitor) >= authority.threshold) { | ||
KeyReverter.cancel(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool all_keys_used() const { return boost::algorithm::all_of_equal(usedKeys, true); } | ||
flat_set<public_key_type> used_keys() const { | ||
auto range = utilities::FilterDataByMarker(signingKeys, usedKeys, true); | ||
return {range.begin(), range.end()}; | ||
} | ||
flat_set<public_key_type> unused_keys() const { | ||
auto range = utilities::FilterDataByMarker(signingKeys, usedKeys, false); | ||
return {range.begin(), range.end()}; | ||
} | ||
}; | ||
|
||
template<typename F> | ||
AuthorityChecker<F> MakeAuthorityChecker(F&& pta, const flat_set<public_key_type>& signingKeys) { | ||
return AuthorityChecker<F>(std::forward<F>(pta), signingKeys); | ||
} | ||
|
||
}} // namespace eos::chain |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters