diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 7ca49ee0..b811ebcd 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -75,6 +75,7 @@ class database_api_impl : public std::enable_shared_from_this // Keys vector> get_key_references( vector key )const; + bool is_public_key_registered(string public_key) const; // Accounts vector> get_accounts(const vector& account_ids)const; @@ -485,6 +486,35 @@ vector> database_api_impl::get_key_references( vectoris_public_key_registered(public_key); +} + +bool database_api_impl::is_public_key_registered(string public_key) const +{ + // Short-circuit + if (public_key.empty()) { + return false; + } + + // Search among all keys using an existing map of *current* account keys + public_key_type key; + try { + key = public_key_type(public_key); + } catch ( ... ) { + // An invalid public key was detected + return false; + } + const auto& idx = _db.get_index_type(); + const auto& aidx = dynamic_cast&>(idx); + const auto& refs = aidx.get_secondary_index(); + auto itr = refs.account_to_key_memberships.find(key); + bool is_known = itr != refs.account_to_key_memberships.end(); + + return is_known; +} + ////////////////////////////////////////////////////////////////////// // // // Accounts // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 95ce16d6..56a486e8 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -212,6 +212,15 @@ class database_api vector> get_key_references( vector key )const; + /** + * Determine whether a textual representation of a public key + * (in Base-58 format) is *currently* linked + * to any *registered* (i.e. non-stealth) account on the blockchain + * @param public_key Public key + * @return Whether a public key is known + */ + bool is_public_key_registered(string public_key) const; + ////////////// // Accounts // ////////////// @@ -592,6 +601,7 @@ FC_API(graphene::app::database_api, // Keys (get_key_references) + (is_public_key_registered) // Accounts (get_accounts) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 919ea9bc..591394d0 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -256,6 +256,26 @@ namespace detail { class wallet_api_impl; } +/*** + * A utility class for performing various state-less actions that are related to wallets + */ +class utility { + public: + /** + * Derive any number of *possible* owner keys from a given brain key. + * + * NOTE: These keys may or may not match with the owner keys of any account. + * This function is merely intended to assist with account or key recovery. + * + * @see suggest_brain_key() + * + * @param brain_key Brain key + * @param number_of_desired_keys Number of desired keys + * @return A list of keys that are deterministically derived from the brainkey + */ + static vector derive_owner_keys_from_brain_key(string brain_key, int number_of_desired_keys = 1); +}; + struct operation_detail { string memo; string description; @@ -576,6 +596,29 @@ class wallet_api */ brain_key_info suggest_brain_key()const; + /** + * Derive any number of *possible* owner keys from a given brain key. + * + * NOTE: These keys may or may not match with the owner keys of any account. + * This function is merely intended to assist with account or key recovery. + * + * @see suggest_brain_key() + * + * @param brain_key Brain key + * @param numberOfDesiredKeys Number of desired keys + * @return A list of keys that are deterministically derived from the brainkey + */ + vector derive_owner_keys_from_brain_key(string brain_key, int number_of_desired_keys = 1) const; + + /** + * Determine whether a textual representation of a public key + * (in Base-58 format) is *currently* linked + * to any *registered* (i.e. non-stealth) account on the blockchain + * @param public_key Public key + * @return Whether a public key is known + */ + bool is_public_key_registered(string public_key) const; + /** Converts a signed_transaction in JSON form to its binary representation. * * TODO: I don't see a broadcast_transaction() function, do we need one? @@ -1565,6 +1608,7 @@ FC_API( graphene::wallet::wallet_api, (import_account_keys) (import_balance) (suggest_brain_key) + (derive_owner_keys_from_brain_key) (register_account) (upgrade_account) (create_account_with_brain_key) @@ -1609,6 +1653,7 @@ FC_API( graphene::wallet::wallet_api, (get_block) (get_account_count) (get_account_history) + (is_public_key_registered) (get_market_history) (get_global_properties) (get_dynamic_global_properties) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index a239d51b..553a0ded 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2717,7 +2717,29 @@ std::string operation_result_printer::operator()(const asset& a) }}} +namespace graphene { namespace wallet { + vector utility::derive_owner_keys_from_brain_key(string brain_key, int number_of_desired_keys) + { + // Safety-check + FC_ASSERT( number_of_desired_keys >= 1 ); + // Create as many derived owner keys as requested + vector results; + brain_key = graphene::wallet::detail::normalize_brain_key(brain_key); + for (int i = 0; i < number_of_desired_keys; ++i) { + fc::ecc::private_key priv_key = graphene::wallet::detail::derive_private_key( brain_key, i ); + + brain_key_info result; + result.brain_priv_key = brain_key; + result.wif_priv_key = key_to_wif( priv_key ); + result.pub_key = priv_key.get_public_key(); + + results.push_back(result); + } + + return results; + } +}} namespace graphene { namespace wallet { @@ -2847,6 +2869,18 @@ brain_key_info wallet_api::suggest_brain_key()const return result; } +vector wallet_api::derive_owner_keys_from_brain_key(string brain_key, int number_of_desired_keys) const +{ + return graphene::wallet::utility::derive_owner_keys_from_brain_key(brain_key, number_of_desired_keys); +} + +bool wallet_api::is_public_key_registered(string public_key) const +{ + bool is_known = my->_remote_db->is_public_key_registered(public_key); + return is_known; +} + + string wallet_api::serialize_transaction( signed_transaction tx )const { return fc::to_hex(fc::raw::pack(tx)); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b03d58a8..827bdb1b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,7 +8,7 @@ endif() file(GLOB UNIT_TESTS "tests/*.cpp") add_executable( chain_test ${UNIT_TESTS} ${COMMON_SOURCES} ) -target_link_libraries( chain_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( chain_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) if(MSVC) set_source_files_properties( tests/serialization_tests.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp new file mode 100644 index 00000000..e2453176 --- /dev/null +++ b/tests/tests/database_api_tests.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(database_api_tests, database_fixture) + + BOOST_AUTO_TEST_CASE(is_registered) { + try { + /*** + * Arrange + */ + auto nathan_private_key = generate_private_key("nathan"); + public_key_type nathan_public = nathan_private_key.get_public_key(); + + auto dan_private_key = generate_private_key("dan"); + public_key_type dan_public = dan_private_key.get_public_key(); + + auto unregistered_private_key = generate_private_key("unregistered"); + public_key_type unregistered_public = unregistered_private_key.get_public_key(); + + + /*** + * Act + */ + create_account("dan", dan_private_key.get_public_key()).id; + create_account("nathan", nathan_private_key.get_public_key()).id; + // Unregistered key will not be registered with any account + + + /*** + * Assert + */ + graphene::app::database_api db_api(db); + + BOOST_CHECK(db_api.is_public_key_registered((string) nathan_public)); + BOOST_CHECK(db_api.is_public_key_registered((string) dan_public)); + BOOST_CHECK(!db_api.is_public_key_registered((string) unregistered_public)); + + } FC_LOG_AND_RETHROW() + } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/wallet_tests.cpp b/tests/tests/wallet_tests.cpp new file mode 100644 index 00000000..554b7297 --- /dev/null +++ b/tests/tests/wallet_tests.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; +using namespace graphene::wallet; + +BOOST_FIXTURE_TEST_SUITE(wallet_tests, database_fixture) + + /*** + * Check the basic behavior of deriving potential owner keys from a brain key + */ + BOOST_AUTO_TEST_CASE(derive_owner_keys_from_brain_key) { + try { + /*** + * Act + */ + int nbr_keys_desired = 3; + vector derived_keys = graphene::wallet::utility::derive_owner_keys_from_brain_key("SOME WORDS GO HERE", nbr_keys_desired); + + + /*** + * Assert: Check the number of derived keys + */ + BOOST_CHECK_EQUAL(nbr_keys_desired, derived_keys.size()); + + /*** + * Assert: Check that each derived key is unique + */ + set set_derived_public_keys; + for (auto info : derived_keys) { + string description = (string) info.pub_key; + set_derived_public_keys.emplace(description); + } + BOOST_CHECK_EQUAL(nbr_keys_desired, set_derived_public_keys.size()); + + /*** + * Assert: Check whether every public key begins with the expected prefix + */ + string expected_prefix = GRAPHENE_ADDRESS_PREFIX; + for (auto info : derived_keys) { + string description = (string) info.pub_key; + BOOST_CHECK_EQUAL(0, description.find(expected_prefix)); + } + + } FC_LOG_AND_RETHROW() + } + +BOOST_AUTO_TEST_SUITE_END()