From 18a484e4bfa37eced5cbc013c978a6f91769af0a Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 21 Apr 2015 15:01:25 -0400 Subject: [PATCH 01/54] cli.hpp: Fix include --- include/fc/rpc/cli.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/fc/rpc/cli.hpp b/include/fc/rpc/cli.hpp index a25e424..dbc6d96 100644 --- a/include/fc/rpc/cli.hpp +++ b/include/fc/rpc/cli.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace fc { namespace rpc { From 381a1258d17dc19913f267c6c7fb3ebc4e0acc69 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 14 Apr 2015 10:37:50 +0200 Subject: [PATCH 02/54] Disable boost static linking, workaround for BOOST_TEST_DYN_LINK --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91567fa..cb14d15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,6 @@ IF( WIN32 ) SET(BOOST_ROOT $ENV{BOOST_ROOT}) # set(Boost_USE_DEBUG_PYTHON ON) set(Boost_USE_MULTITHREADED ON) - set(Boost_USE_STATIC_LIBS ON) set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries FIND_PACKAGE(Boost 1.53 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) @@ -62,7 +61,6 @@ IF( WIN32 ) ELSE(WIN32) MESSAGE(STATUS "Configuring fc to build on Unix/Apple") - SET(Boost_USE_STATIC_LIBS ON) LIST(APPEND BOOST_COMPONENTS coroutine) FIND_PACKAGE(Boost 1.53 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) @@ -236,6 +234,14 @@ target_include_directories(fc #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) +IF(NOT BOOST_unit_test_framework_LIBRARY MATCHES "\\.a$") +IF(WIN32) +add_definitions(/DBOOST_TEST_DYN_LINK) +ELSE(WIN32) +add_definitions(-DBOOST_TEST_DYN_LINK) +ENDIF(WIN32) +ENDIF() + add_executable( api tests/api.cpp ) target_link_libraries( api fc ) From 3e7b554f65266644b96e08cc88f6bd79709b03c9 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 14 Apr 2015 22:06:15 +0200 Subject: [PATCH 03/54] Fixed variable case --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb14d15..414a6c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,7 @@ target_include_directories(fc #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) -IF(NOT BOOST_unit_test_framework_LIBRARY MATCHES "\\.a$") +IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.a$") IF(WIN32) add_definitions(/DBOOST_TEST_DYN_LINK) ELSE(WIN32) From 8e8dd9265a7d3fc994b885787d870d97991ae89e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sun, 19 Apr 2015 10:14:53 +0200 Subject: [PATCH 04/54] Handle .lib on windows --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 414a6c5..36d70b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,7 @@ target_include_directories(fc #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) -IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.a$") +IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.(a|lib)$") IF(WIN32) add_definitions(/DBOOST_TEST_DYN_LINK) ELSE(WIN32) From b8341a006e3e51c0249b9fce13535c4c2a1976de Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Mon, 27 Apr 2015 14:57:45 +0200 Subject: [PATCH 05/54] Set Boost_USE_STATIC_LIBS to previous default ON but configurable --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d70b9..2b20e4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ SET (ORIGINAL_LIB_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) SET(BOOST_COMPONENTS) LIST(APPEND BOOST_COMPONENTS thread date_time system filesystem program_options signals serialization chrono unit_test_framework context locale iostreams) +SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) IF( WIN32 ) MESSAGE(STATUS "Configuring fc to build on Win32") From 6c589678df1f2b25de79042ada1e6c563317a8ee Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 29 Apr 2015 13:17:38 -0400 Subject: [PATCH 06/54] adding extra reflection helpers --- include/fc/reflect/typename.hpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/fc/reflect/typename.hpp b/include/fc/reflect/typename.hpp index ca514c9..a7d3503 100644 --- a/include/fc/reflect/typename.hpp +++ b/include/fc/reflect/typename.hpp @@ -1,6 +1,10 @@ #pragma once #include +#include #include +#include +#include + namespace fc { class value; class exception; @@ -31,6 +35,27 @@ namespace fc { return n.c_str(); } }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("flat_set<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("optional<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("std::map<") + get_typename::name() + ","+get_typename::name()+">"; + return n.c_str(); + } + }; struct signed_int; struct unsigned_int; From 80de0987d77b36e40acb9b0a7bf835a0cb7c6c90 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Wed, 29 Apr 2015 15:41:55 -0400 Subject: [PATCH 07/54] Add readline support to fc::rpc::cli --- CMakeLists.txt | 25 +++++++++++- CMakeModules/FindReadline.cmake | 47 ++++++++++++++++++++++ include/fc/rpc/cli.hpp | 18 +++++++-- src/rpc/cli.cpp | 69 +++++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 CMakeModules/FindReadline.cmake create mode 100644 src/rpc/cli.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b20e4b..b68d841 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,8 @@ ELSE(WIN32) ENDIF(NOT APPLE) ENDIF(WIN32) + + IF(NOT "$ENV{OPENSSL_ROOT_DIR}" STREQUAL "") set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR} ) set(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include) @@ -123,6 +125,7 @@ set( fc_sources src/interprocess/file_mapping.cpp src/interprocess/mmap_struct.cpp src/rpc/json_connection.cpp + src/rpc/cli.cpp src/log/log_message.cpp src/log/logger.cpp src/log/appender.cpp @@ -195,6 +198,25 @@ add_subdirectory( vendor/udt4 ) setup_library( fc SOURCES ${sources} LIBRARY_TYPE STATIC DONT_INSTALL_LIBRARY ) +# begin readline stuff +find_package(Curses) +find_package(Readline) + +file(GLOB HEADERS "include/bts/cli/*.hpp") + +if (READLINE_FOUND) + target_compile_definitions (fc PRIVATE HAVE_READLINE) + set(readline_libraries ${Readline_LIBRARY}) + if (CURSES_FOUND) + list(APPEND readline_libraries ${CURSES_LIBRARY}) + endif() + set(readline_includes ${Readline_INCLUDE_DIR}) +endif() +if(WIN32) + target_compile_definitions( fc PRIVATE _CRT_NONSTDC_NO_DEPRECATE ) +endif(WIN32) +# end readline stuff + IF(WIN32) target_compile_definitions(fc PUBLIC WIN32 NOMINMAX _WIN32_WINNT=0x0501 _CRT_SECURE_NO_WARNINGS _SCL_SERCURE_NO_WARNINGS @@ -221,6 +243,7 @@ target_include_directories(fc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${Boost_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} + "${readline_includes}" PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} #${CMAKE_CURRENT_SOURCE_DIR}/vendor/scrypt-jane @@ -233,7 +256,7 @@ target_include_directories(fc ) #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) -target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) +target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries}) IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.(a|lib)$") IF(WIN32) diff --git a/CMakeModules/FindReadline.cmake b/CMakeModules/FindReadline.cmake new file mode 100644 index 0000000..745cfe5 --- /dev/null +++ b/CMakeModules/FindReadline.cmake @@ -0,0 +1,47 @@ +# - Try to find readline include dirs and libraries +# +# Usage of this module as follows: +# +# find_package(Readline) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Readline_ROOT_DIR Set this variable to the root installation of +# readline if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# READLINE_FOUND System has readline, include and lib dirs found +# Readline_INCLUDE_DIR The readline include directories. +# Readline_LIBRARY The readline library. + +find_path(Readline_ROOT_DIR + NAMES include/readline/readline.h +) + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + HINTS ${Readline_ROOT_DIR}/include +) + +find_library(Readline_LIBRARY + NAMES readline + HINTS ${Readline_ROOT_DIR}/lib +) + +if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + set(READLINE_FOUND TRUE) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + FIND_LIBRARY(Readline_LIBRARY NAMES readline) + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) + MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + +mark_as_advanced( + Readline_ROOT_DIR + Readline_INCLUDE_DIR + Readline_LIBRARY +) diff --git a/include/fc/rpc/cli.hpp b/include/fc/rpc/cli.hpp index dbc6d96..f4bb7a5 100644 --- a/include/fc/rpc/cli.hpp +++ b/include/fc/rpc/cli.hpp @@ -6,6 +6,8 @@ #include #include +#include + namespace fc { namespace rpc { /** @@ -48,16 +50,24 @@ namespace fc { namespace rpc { { _result_formatters[method] = formatter; } + + virtual void getline( const fc::string& prompt, fc::string& line ); + private: void run() { - while( !fc::cin.eof() && !_run_complete.canceled() ) + while( !_run_complete.canceled() ) { try { - std::cout << ">>> "; - std::cout.flush(); std::string line; - fc::getline( fc::cin, line ); + try + { + getline( ">>> ", line ); + } + catch ( const fc::eof_exception& e ) + { + break; + } std::cout << line << "\n"; line += char(EOF); fc::variants args = fc::json::variants_from_string(line);; diff --git a/src/rpc/cli.cpp b/src/rpc/cli.cpp new file mode 100644 index 0000000..9441894 --- /dev/null +++ b/src/rpc/cli.cpp @@ -0,0 +1,69 @@ + +#include + +#include + +#ifndef WIN32 +#include +#endif + +#ifdef HAVE_READLINE +# include +# include +// I don't know exactly what version of readline we need. I know the 4.2 version that ships on some macs is +// missing some functions we require. We're developing against 6.3, but probably anything in the 6.x +// series is fine +# if RL_VERSION_MAJOR < 6 +# ifdef _MSC_VER +# pragma message("You have an old version of readline installed that might not support some of the features we need") +# pragma message("Readline support will not be compiled in") +# else +# warning "You have an old version of readline installed that might not support some of the features we need" +# warning "Readline support will not be compiled in" +# endif +# undef HAVE_READLINE +# endif +#endif + +namespace fc { namespace rpc { + +void cli::getline( + const fc::string& prompt, + fc::string& line + ) +{ + // getting file descriptor for C++ streams is near impossible + // so we just assume it's the same as the C stream... +#ifdef HAVE_READLINE +#ifndef WIN32 + if( isatty( fileno( stdin ) ) ) +#else + // it's implied by + // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx + // that this is the proper way to do this on Windows, but I have + // no access to a Windows compiler and thus, + // no idea if this actually works + if( _isatty( _fileno( stdin ) ) ) +#endif + { + char* line_read = nullptr; + std::cout.flush(); //readline doesn't use cin, so we must manually flush _out + line_read = readline(prompt.c_str()); + if( line_read == nullptr ) + FC_THROW_EXCEPTION( fc::eof_exception, "" ); + if( *line_read ) + add_history(line_read); + line = line_read; + free(line_read); + } + else +#endif + { + std::cout << prompt; + // sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline"); + fc::getline( fc::cin, line ); + return; + } +} + +} } From 2195f191e45fa1ac00a55dc9b00db1e7dab8365f Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 1 May 2015 16:17:49 -0400 Subject: [PATCH 08/54] Add increment/decrement to safe --- include/fc/safe.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/fc/safe.hpp b/include/fc/safe.hpp index 44effd6..2482621 100644 --- a/include/fc/safe.hpp +++ b/include/fc/safe.hpp @@ -41,6 +41,11 @@ namespace fc { return safe(-value); } + safe operator++(int) { safe bak = *this; *this += 1; return bak; } + safe& operator++() { return *this += 1; } + safe operator--(int) { safe bak = *this; *this -= 1; return bak; } + safe& operator--() { return *this -= 1; } + friend safe operator - ( const safe& a, const safe& b ) { safe tmp(a); tmp -= b; return tmp; From 2e7e14df1ca42053b1cb102d7cda63e734c24ed1 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sat, 2 May 2015 15:37:36 -0400 Subject: [PATCH 09/54] Add missing #include --- include/fc/safe.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/fc/safe.hpp b/include/fc/safe.hpp index 2482621..9f165b4 100644 --- a/include/fc/safe.hpp +++ b/include/fc/safe.hpp @@ -2,6 +2,8 @@ #include #include +#include + namespace fc { /** From e9824e174082d65117fd51d17377675d06bab7de Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Mon, 4 May 2015 13:36:15 -0400 Subject: [PATCH 10/54] Add missing include for isatty on win32 --- src/rpc/cli.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rpc/cli.cpp b/src/rpc/cli.cpp index 9441894..1525cb2 100644 --- a/src/rpc/cli.cpp +++ b/src/rpc/cli.cpp @@ -23,6 +23,9 @@ # endif # undef HAVE_READLINE # endif +# ifdef WIN32 +# include +# endif #endif namespace fc { namespace rpc { From 72288a25b19a12fcf530e1309f5ce44b293c7d4d Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Mon, 4 May 2015 14:07:22 -0400 Subject: [PATCH 11/54] making sure getline doesn't blog --- src/network/http/websocket.cpp | 5 +++-- src/rpc/cli.cpp | 22 +++++++++++++--------- tests/api.cpp | 5 ++++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 5987027..283730b 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -73,6 +73,7 @@ namespace fc { namespace http { virtual void send_message( const std::string& message )override { + idump((message)); auto ec = _ws_connection->send( message ); FC_ASSERT( !ec, "websocket send failed: ${msg}", ("msg",ec.message() ) ); } @@ -103,7 +104,7 @@ namespace fc { namespace http { _server_thread.async( [&](){ auto current_con = _connections.find(hdl); assert( current_con != _connections.end() ); - //wdump(("server")(msg->get_payload())); + wdump(("server")(msg->get_payload())); current_con->second->on_message( msg->get_payload() ); }).wait(); }); @@ -154,7 +155,7 @@ namespace fc { namespace http { _client.clear_access_channels( websocketpp::log::alevel::all ); _client.set_message_handler( [&]( connection_hdl hdl, message_ptr msg ){ _client_thread.async( [&](){ - // wdump((msg->get_payload())); + wdump((msg->get_payload())); _connection->on_message( msg->get_payload() ); }).wait(); }); diff --git a/src/rpc/cli.cpp b/src/rpc/cli.cpp index 9441894..996f976 100644 --- a/src/rpc/cli.cpp +++ b/src/rpc/cli.cpp @@ -1,5 +1,6 @@ #include +#include #include @@ -46,15 +47,18 @@ void cli::getline( if( _isatty( _fileno( stdin ) ) ) #endif { - char* line_read = nullptr; - std::cout.flush(); //readline doesn't use cin, so we must manually flush _out - line_read = readline(prompt.c_str()); - if( line_read == nullptr ) - FC_THROW_EXCEPTION( fc::eof_exception, "" ); - if( *line_read ) - add_history(line_read); - line = line_read; - free(line_read); + static fc::thread getline_thread("getline"); + getline_thread.async( [&](){ + char* line_read = nullptr; + std::cout.flush(); //readline doesn't use cin, so we must manually flush _out + line_read = readline(prompt.c_str()); + if( line_read == nullptr ) + FC_THROW_EXCEPTION( fc::eof_exception, "" ); + if( *line_read ) + add_history(line_read); + line = line_read; + free(line_read); + }).wait(); } else #endif diff --git a/tests/api.cpp b/tests/api.cpp index 533e52b..4fcd8b5 100644 --- a/tests/api.cpp +++ b/tests/api.cpp @@ -9,9 +9,10 @@ class calculator int32_t add( int32_t a, int32_t b ); // not implemented int32_t sub( int32_t a, int32_t b ); // not implemented void on_result( const std::function& cb ); + void on_result2( const std::function& cb, int test ); }; -FC_API( calculator, (add)(sub)(on_result) ) +FC_API( calculator, (add)(sub)(on_result)(on_result2) ) class login_api @@ -35,6 +36,7 @@ class some_calculator int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; } int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; } void on_result( const std::function& cb ) { wlog( "set callback" ); _cb = cb; return ; } + void on_result2( const std::function& cb, int test ){} std::function _cb; }; class variant_calculator @@ -43,6 +45,7 @@ class variant_calculator double add( fc::variant a, fc::variant b ) { return a.as_double()+b.as_double(); } double sub( fc::variant a, fc::variant b ) { return a.as_double()-b.as_double(); } void on_result( const std::function& cb ) { wlog("set callback"); _cb = cb; return ; } + void on_result2( const std::function& cb, int test ){} std::function _cb; }; From 4df08d8efe655b1fb4b367a729d1c855b5e433f2 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 6 May 2015 16:34:55 -0400 Subject: [PATCH 12/54] fix crash in websocket --- include/fc/rpc/api_connection.hpp | 20 +++++++++++++------- include/fc/rpc/websocket_api.hpp | 4 ++++ src/network/http/websocket.cpp | 9 ++++++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/include/fc/rpc/api_connection.hpp b/include/fc/rpc/api_connection.hpp index 8b0251b..fd67072 100644 --- a/include/fc/rpc/api_connection.hpp +++ b/include/fc/rpc/api_connection.hpp @@ -91,7 +91,7 @@ namespace fc { return _methods[method_id](args); } - fc::api_connection& get_connection(){ return *_api_connection; } + fc::api_connection& get_connection(){ auto tmp = _api_connection.lock(); FC_ASSERT( tmp, "connection closed"); return *tmp; } private: @@ -133,7 +133,7 @@ namespace fc { struct api_visitor { - api_visitor( generic_api& a, const std::shared_ptr& s ):api(a),_api_con(s){ } + api_visitor( generic_api& a, const std::weak_ptr& s ):api(a),_api_con(s){ } template std::function to_generic( const std::function(Args...)>& f )const; @@ -154,11 +154,11 @@ namespace fc { } generic_api& api; - const std::shared_ptr& _api_con; + const std::weak_ptr& _api_con; }; - std::shared_ptr _api_connection; + std::weak_ptr _api_connection; fc::any _api; std::map< std::string, uint32_t > _by_name; std::vector< std::function > _methods; @@ -325,7 +325,7 @@ namespace fc { generic_api::generic_api( const Api& a, const std::shared_ptr& c ) :_api_connection(c),_api(a) { - boost::any_cast(a)->visit( api_visitor( *this, _api_connection ) ); + boost::any_cast(a)->visit( api_visitor( *this, c ) ); } template @@ -335,8 +335,11 @@ namespace fc { auto api_con = _api_con; auto gapi = &api; return [=]( const variants& args ) { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); - return api_con->register_api( api_result ); + return con->register_api( api_result ); }; } template @@ -346,9 +349,12 @@ namespace fc { auto api_con = _api_con; auto gapi = &api; return [=]( const variants& args )-> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); if( api_result ) - return api_con->register_api( *api_result ); + return con->register_api( *api_result ); return variant(); }; } diff --git a/include/fc/rpc/websocket_api.hpp b/include/fc/rpc/websocket_api.hpp index 202a2cb..b4ab6c5 100644 --- a/include/fc/rpc/websocket_api.hpp +++ b/include/fc/rpc/websocket_api.hpp @@ -10,6 +10,10 @@ namespace fc { namespace rpc { class websocket_api_connection : public api_connection { public: + ~websocket_api_connection() + { + } + websocket_api_connection( fc::http::websocket_connection& c ) :_connection(c) { diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 283730b..0250c8a 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -69,7 +69,12 @@ namespace fc { namespace http { { public: websocket_connection_impl( T con ) - :_ws_connection(con){} + :_ws_connection(con){ + } + + ~websocket_connection_impl() + { + } virtual void send_message( const std::string& message )override { @@ -110,6 +115,7 @@ namespace fc { namespace http { }); _server.set_close_handler( [&]( connection_hdl hdl ){ _server_thread.async( [&](){ + _connections[hdl]->closed(); _connections.erase( hdl ); }).wait(); }); @@ -118,6 +124,7 @@ namespace fc { namespace http { if( _server.is_listening() ) { _server_thread.async( [&](){ + _connections[hdl]->closed(); _connections.erase( hdl ); }).wait(); } From 5dabe6ba2c1c499fd7df342d1846a5e8340c62b0 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 6 May 2015 16:37:38 -0400 Subject: [PATCH 13/54] better error messages --- include/fc/rpc/api_connection.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fc/rpc/api_connection.hpp b/include/fc/rpc/api_connection.hpp index fd67072..c037c22 100644 --- a/include/fc/rpc/api_connection.hpp +++ b/include/fc/rpc/api_connection.hpp @@ -112,14 +112,14 @@ namespace fc { template R call_generic( const std::function,Args...)>& f, variants::const_iterator a0, variants::const_iterator e ) { - FC_ASSERT( a0 != e ); + FC_ASSERT( a0 != e, "too few arguments passed to method" ); detail::callback_functor arg0( *this, a0->as() ); return call_generic( this->bind_first_arg,Args...>( f, std::function(arg0) ), a0+1, e ); } template R call_generic( const std::function&,Args...)>& f, variants::const_iterator a0, variants::const_iterator e ) { - FC_ASSERT( a0 != e ); + FC_ASSERT( a0 != e, "too few arguments passed to method" ); detail::callback_functor arg0( get_connection(), a0->as() ); return call_generic( this->bind_first_arg&,Args...>( f, arg0 ), a0+1, e ); } @@ -127,7 +127,7 @@ namespace fc { template R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) { - FC_ASSERT( a0 != e ); + FC_ASSERT( a0 != e, "too few arguments passed to method" ); return call_generic( this->bind_first_arg( f, a0->as< typename std::decay::type >() ), a0+1, e ); } From 3bf3b0c9b4c51ac6a8ec5c17598242fccd6e444e Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 5 May 2015 18:40:31 -0400 Subject: [PATCH 14/54] uint128.hpp: Reflect uint128 --- include/fc/uint128.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/fc/uint128.hpp b/include/fc/uint128.hpp index 6071f64..41e6925 100644 --- a/include/fc/uint128.hpp +++ b/include/fc/uint128.hpp @@ -102,10 +102,9 @@ namespace fc static void full_product( const uint128& a, const uint128& b, uint128& result_hi, uint128& result_lo ); - private: - uint64_t hi; - uint64_t lo; - + // fields must be public for serialization + uint64_t hi; + uint64_t lo; }; static_assert( sizeof(uint128) == 2*sizeof(uint64_t), "validate packing assumptions" ); @@ -139,6 +138,8 @@ namespace std }; } +FC_REFLECT( fc::uint128_t, (hi)(lo) ) + #ifdef _MSC_VER #pragma warning (pop) #endif ///_MSC_VER From 7dcfa9a9104189023c85e7d5e69d528930104ed9 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Wed, 6 May 2015 16:51:54 -0400 Subject: [PATCH 15/54] safe.hpp: Add comment noting that safe is not compatible with uint128_t --- include/fc/safe.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/fc/safe.hpp b/include/fc/safe.hpp index 9f165b4..bb86a71 100644 --- a/include/fc/safe.hpp +++ b/include/fc/safe.hpp @@ -10,6 +10,9 @@ namespace fc { * This type is designed to provide automatic checks for * integer overflow and default initialization. It will * throw an exception on overflow conditions. + * + * It can only be used on built-in types. In particular, + * safe is buggy and should not be used. */ template struct safe From 8bcaa01541cf0a0ccd54af00054c87802e6bc33e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 6 Mar 2015 12:27:51 +0100 Subject: [PATCH 16/54] Mark internal helper functions as static --- src/crypto/elliptic.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/elliptic.cpp b/src/crypto/elliptic.cpp index 51e8b20..fe11b50 100644 --- a/src/crypto/elliptic.cpp +++ b/src/crypto/elliptic.cpp @@ -56,7 +56,7 @@ namespace fc { namespace ecc { EC_KEY* _key; }; } - void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) { if (*olen < SHA512_DIGEST_LENGTH) { return NULL; @@ -68,7 +68,7 @@ namespace fc { namespace ecc { // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields // recid selects which key is recovered // if check is non-zero, additional checks are performed - int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) + static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) { if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); From f17444d90bc256fe6e31dd79180c8780dffb825c Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 6 Mar 2015 14:28:25 +0100 Subject: [PATCH 17/54] Added ECC_IMPL switch + interop test --- CMakeLists.txt | 3 +- include/fc/crypto/elliptic.hpp | 6 +- src/crypto/elliptic_openssl.cpp | 668 ++++++++++++++++++ .../{elliptic.cpp => elliptic_secp256k1.cpp} | 0 tests/ecc_test.cpp | 74 +- tests/ecc_test.interop.data | Bin 0 -> 1269000 bytes 6 files changed, 746 insertions(+), 5 deletions(-) create mode 100644 src/crypto/elliptic_openssl.cpp rename src/crypto/{elliptic.cpp => elliptic_secp256k1.cpp} (100%) create mode 100644 tests/ecc_test.interop.data diff --git a/CMakeLists.txt b/CMakeLists.txt index b68d841..de4735e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ SET( DEFAULT_LIBRARY_INSTALL_DIR lib/ ) SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ ) SET( CMAKE_DEBUG_POSTFIX _debug ) SET( BUILD_SHARED_LIBS NO ) +SET( ECC_IMPL openssl ) # openssl or secp256k1 set(platformBitness 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -150,7 +151,7 @@ set( fc_sources src/crypto/sha512.cpp src/crypto/dh.cpp src/crypto/blowfish.cpp - src/crypto/elliptic.cpp + src/crypto/elliptic_${ECC_IMPL}.cpp src/crypto/rand.cpp src/crypto/salsa20.cpp #src/crypto/scrypt.cpp diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp index 2225fb9..69c1bfb 100644 --- a/include/fc/crypto/elliptic.hpp +++ b/include/fc/crypto/elliptic.hpp @@ -31,7 +31,7 @@ namespace fc { public_key(); public_key(const public_key& k); ~public_key(); - bool verify( const fc::sha256& digest, const signature& sig ); +// bool verify( const fc::sha256& digest, const signature& sig ); public_key_data serialize()const; public_key_point_data serialize_ecc_point()const; @@ -103,9 +103,9 @@ namespace fc { */ fc::sha512 get_shared_secret( const public_key& pub )const; - signature sign( const fc::sha256& digest )const; +// signature sign( const fc::sha256& digest )const; compact_signature sign_compact( const fc::sha256& digest )const; - bool verify( const fc::sha256& digest, const signature& sig ); +// bool verify( const fc::sha256& digest, const signature& sig ); public_key get_public_key()const; diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp new file mode 100644 index 0000000..80ec0f7 --- /dev/null +++ b/src/crypto/elliptic_openssl.cpp @@ -0,0 +1,668 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +namespace fc { namespace ecc { + namespace detail + { + class public_key_impl + { + public: + public_key_impl() + :_key(nullptr) + { + static int init = init_openssl(); + } + + ~public_key_impl() + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + } + } + public_key_impl( const public_key_impl& cpy ) + { + _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + } + EC_KEY* _key; + }; + class private_key_impl + { + public: + private_key_impl() + :_key(nullptr) + { + static int init = init_openssl(); + } + ~private_key_impl() + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + } + } + private_key_impl( const private_key_impl& cpy ) + { + _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + } + EC_KEY* _key; + }; + } + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + { + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); + } + + // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields + // recid selects which key is recovered + // if check is non-zero, additional checks are performed + static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) + { + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + + err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; + } + + + int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) + { + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); + } + +/* + public_key::public_key() + :my( new detail::public_key_impl() ) + { + } + + public_key::public_key( fc::bigint pub_x, fc::bigint pub_y ) + :my( new detail::public_key_impl() ) + { + } + + public_key::~public_key() + { + } + */ + + public_key public_key::mult( const fc::sha256& digest ) + { + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + + ssl_bignum z; + BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest + ssl_bignum one; + BN_one(one); + bn_ctx ctx(BN_CTX_new()); + + ec_point result(EC_POINT_new(group)); + EC_POINT_mul(group, result, z, master_pub, one, ctx); + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + + return rtn; + } + bool public_key::valid()const + { + return my->_key != nullptr; + } + public_key public_key::add( const fc::sha256& digest )const + { + try { + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + + fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); + + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + if( digest_bi > fc::bigint(order) ) + { + FC_THROW_EXCEPTION( exception, "digest > group order" ); + } + + + public_key digest_key = private_key::regenerate(digest).get_public_key(); + const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); + + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + + ssl_bignum z; + BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest + ssl_bignum one; + BN_one(one); + + ec_point result(EC_POINT_new(group)); + EC_POINT_add(group, result, digest_point, master_pub, ctx); + + if (EC_POINT_is_at_infinity(group, result)) + { + FC_THROW_EXCEPTION( exception, "point at infinity" ); + } + + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + return rtn; + } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + } + + std::string public_key::to_base58() const + { + public_key_data key = serialize(); + uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; + assert(key.size() + sizeof(check) == 37); + array data; + memcpy(data.data, key.begin(), key.size()); + memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); + return fc::to_base58(data.begin(), data.size()); + } + + public_key public_key::from_base58( const std::string& b58 ) + { + array data; + size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); + FC_ASSERT( s == sizeof(data) ); + + public_key_data key; + uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; + FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); + memcpy( (char*)key.data, data.data, sizeof(key) ); + return public_key(key); + } + + private_key::private_key() + {} + + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) + { + ssl_bignum z; + BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); + + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + + // secexp = (seed + z) % order + ssl_bignum secexp; + BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); + BN_add(secexp, secexp, z); + BN_mod(secexp, secexp, order, ctx); + + fc::sha256 secret; + assert(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); + auto shift = sizeof(secret) - BN_num_bytes(secexp); + BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); + return regenerate( secret ); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) + { + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); + } + return self; + } + + fc::sha256 private_key::get_secret()const + { + if( !my->_key ) + { + return fc::sha256(); + } + + fc::sha256 sec; + const BIGNUM* bn = EC_KEY_get0_private_key(my->_key); + if( bn == NULL ) + { + FC_THROW_EXCEPTION( exception, "get private key failed" ); + } + int nbytes = BN_num_bytes(bn); + BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); + return sec; + } + + private_key private_key::generate() + { + private_key self; + EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + self.my->_key = k; + if( !EC_KEY_generate_key( self.my->_key ) ) + { + FC_THROW_EXCEPTION( exception, "ecc key generation error" ); + + } + +#if 0 + = bigint( EC_KEY_get0_private_key( k ); + EC_POINT* pub = EC_KEY_get0_public_key( k ); + EC_GROUP* group = EC_KEY_get0_group( k ); + + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr/*ctx*/ ); + + EC_KEY_free(k); +#endif + + return self; + } + +// signature private_key::sign( const fc::sha256& digest )const +// { +// unsigned int buf_len = ECDSA_size(my->_key); +//// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); +// signature sig; +// assert( buf_len == sizeof(sig) ); +// +// if( !ECDSA_sign( 0, +// (const unsigned char*)&digest, sizeof(digest), +// (unsigned char*)&sig, &buf_len, my->_key ) ) +// { +// FC_THROW_EXCEPTION( exception, "signing error" ); +// } +// +// +// return sig; +// } +// bool public_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key_data public_key::serialize()const + { + public_key_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ + /*assert( nbytes == 33 )*/ + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + return dat; + /* + EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); + EC_GROUP* group = EC_KEY_get0_group( my->_key ); + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); + */ + } + public_key_point_data public_key::serialize_ecc_point()const + { + public_key_point_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + return dat; + } + + public_key::public_key() + { + } + public_key::~public_key() + { + } + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + /*my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); */ + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + public_key::public_key( const public_key_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + +// bool private_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key private_key::get_public_key()const + { + public_key pub; + pub.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key( pub.my->_key, EC_KEY_get0_public_key( my->_key ) ); + return pub; + } + + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + fc::sha512 buf; + ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); + return buf; + } + + private_key::~private_key() + { + } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + ECDSA_SIG *sig = ECDSA_SIG_new(); + BN_bin2bn(&c.data[1],32,sig->r); + BN_bin2bn(&c.data[33],32,sig->s); + + if( check_canonical ) + { + FC_ASSERT( !(c.data[1] & 0x80), "signature is not canonical" ); + FC_ASSERT( !(c.data[1] == 0 && !(c.data[2] & 0x80)), "signature is not canonical" ); + FC_ASSERT( !(c.data[33] & 0x80), "signature is not canonical" ); + FC_ASSERT( !(c.data[33] == 0 && !(c.data[34] & 0x80)), "signature is not canonical" ); + } + + my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); + + if (nV >= 31) + { + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + nV -= 4; +// fprintf( stderr, "compressed\n" ); + } + + if (ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) + { + ECDSA_SIG_free(sig); + return; + } + ECDSA_SIG_free(sig); + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + } + + compact_signature private_key::sign_compact( const fc::sha256& digest )const + { + try { + FC_ASSERT( my->_key != nullptr ); + auto my_pub_key = get_public_key().serialize(); // just for good measure + //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + while( true ) + { + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + compact_signature csig; + // memset( csig.data, 0, sizeof(csig) ); + + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + for (int i=0; i<4; i++) + { + public_key keyRec; + keyRec.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if (ECDSA_SIG_recover_key_GFp(keyRec.my->_key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) + { + if (keyRec.serialize() == my_pub_key ) + { + nRecId = i; + break; + } + } + } + + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); + if( lenR != 32 ) { free(result); continue; } + if( lenS != 32 ) { free(result); continue; } + //idump( (33-(nBitsR+7)/8) ); + //idump( (65-(nBitsS+7)/8) ); + //idump( (sizeof(csig) ) ); + memcpy( &csig.data[1], &result[4], lenR ); + memcpy( &csig.data[33], &result[6+lenR], lenS ); + //idump( (csig.data[33]) ); + //idump( (csig.data[1]) ); + free(result); + //idump( (nRecId) ); + csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); + /* + idump( (csig) ); + auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); + auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); + idump( (rlen)(slen) ); + */ + } + return csig; + } // while true + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); + } + + private_key& private_key::operator=( private_key&& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = pk.my->_key; + pk.my->_key = nullptr; + return *this; + } + public_key::public_key( const public_key& pk ) + :my(pk.my) + { + } + public_key::public_key( public_key&& pk ) + :my( fc::move( pk.my) ) + { + } + private_key::private_key( const private_key& pk ) + :my(pk.my) + { + } + private_key::private_key( private_key&& pk ) + :my( fc::move( pk.my) ) + { + } + + public_key& public_key::operator=( public_key&& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = pk.my->_key; + pk.my->_key = nullptr; + return *this; + } + public_key& public_key::operator=( const public_key& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = EC_KEY_dup(pk.my->_key); + return *this; + } + private_key& private_key::operator=( const private_key& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = EC_KEY_dup(pk.my->_key); + return *this; + } + +} + void to_variant( const ecc::private_key& var, variant& vo ) + { + vo = var.get_secret(); + } + void from_variant( const variant& var, ecc::private_key& vo ) + { + fc::sha256 sec; + from_variant( var, sec ); + vo = ecc::private_key::regenerate(sec); + } + + void to_variant( const ecc::public_key& var, variant& vo ) + { + vo = var.serialize(); + } + void from_variant( const variant& var, ecc::public_key& vo ) + { + ecc::public_key_data dat; + from_variant( var, dat ); + vo = ecc::public_key(dat); + } + + +} diff --git a/src/crypto/elliptic.cpp b/src/crypto/elliptic_secp256k1.cpp similarity index 100% rename from src/crypto/elliptic.cpp rename to src/crypto/elliptic_secp256k1.cpp diff --git a/tests/ecc_test.cpp b/tests/ecc_test.cpp index 4ae6217..99c728f 100644 --- a/tests/ecc_test.cpp +++ b/tests/ecc_test.cpp @@ -1,9 +1,69 @@ #include #include #include +#include + +static std::fstream interop_data; +static bool write_mode = false; + +static void interop_do(const char * const data, size_t len) { + static char buffer[256]; + + if (!interop_data.is_open()) { return; } + + FC_ASSERT(len < sizeof(buffer)); + if (write_mode) { + interop_data.write(data, len); + return; + } + + interop_data.read(buffer, len); + FC_ASSERT(!interop_data.eof()); + FC_ASSERT(!memcmp(data, buffer, len)); +} + +static void interop_do(const fc::ecc::public_key_data &data) { + interop_do(data.begin(), data.size()); +} + +static void interop_do(const fc::ecc::private_key_secret &data) { + interop_do(data.data(), 32); +} + +static void interop_do(const fc::ecc::public_key_point_data &data) { + interop_do(data.begin(), data.size()); +} + +//static void interop_do(const fc::ecc::signature &data) { +// interop_do(data.begin(), data.size()); +//} + +static void interop_do(fc::ecc::compact_signature &data) { + if (write_mode) { + interop_data.write((char*) data.begin(), data.size()); + return; + } + + interop_data.read((char*) data.begin(), data.size()); +} + +static void interop_file(const char * const name) { + interop_data.open(name, std::fstream::in | std::fstream::binary); + if (!interop_data.fail()) { return; } + + write_mode = true; + interop_data.open(name, std::fstream::out | std::fstream::binary); + if (!interop_data.fail()) { return; } + + std::cerr << "Can't read nor write " << name << "\n"; +} int main( int argc, char** argv ) { + if (argc > 2) { + interop_file(argv[2]); + } + for( uint32_t i = 0; i < 3000; ++ i ) { try { @@ -12,16 +72,25 @@ int main( int argc, char** argv ) std::string pass(argv[1]); fc::sha256 h = fc::sha256::hash( pass.c_str(), pass.size() ); fc::ecc::private_key priv = fc::ecc::private_key::generate_from_seed(h); + interop_do(priv.get_secret()); fc::ecc::public_key pub = priv.get_public_key(); + interop_do(pub.serialize()); + interop_do(pub.serialize_ecc_point()); pass += "1"; fc::sha256 h2 = fc::sha256::hash( pass.c_str(), pass.size() ); fc::ecc::public_key pub1 = pub.mult( h2 ); + interop_do(pub1.serialize()); + interop_do(pub1.serialize_ecc_point()); fc::ecc::private_key priv1 = fc::ecc::private_key::generate_from_seed(h, h2); + interop_do(priv1.get_secret()); auto sig = priv.sign_compact( h ); + interop_do(sig); auto recover = fc::ecc::public_key( sig, h ); - FC_ASSERT( recover == priv.get_public_key() ); + interop_do(recover.serialize()); + interop_do(recover.serialize_ecc_point()); + FC_ASSERT( recover == pub ); } catch ( const fc::exception& e ) { @@ -29,6 +98,9 @@ int main( int argc, char** argv ) } } + if (interop_data.is_open()) { + interop_data.close(); + } return 0; } diff --git a/tests/ecc_test.interop.data b/tests/ecc_test.interop.data new file mode 100644 index 0000000000000000000000000000000000000000..d8c9277484d7c0eb38994e3b0398d759087a9dce GIT binary patch literal 1269000 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%0DKry^s+1msCxrh?BcA>hyhV(38wgH;O= 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

{X4l24n(+iI=KE_;gY5sBL;p!*G+x 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

NPzCC!b!yZ!vGP!da^ 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ⅈ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 Date: Sat, 7 Mar 2015 14:48:45 +0100 Subject: [PATCH 18/54] Intermediate version --- CMakeLists.txt | 9 +- include/fc/crypto/elliptic.hpp | 8 +- src/crypto/elliptic_common.cpp | 118 +++++ src/crypto/elliptic_openssl.cpp | 135 +----- src/crypto/elliptic_secp256k1.cpp | 688 ++++++++---------------------- 5 files changed, 331 insertions(+), 627 deletions(-) create mode 100644 src/crypto/elliptic_common.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index de4735e..6adb05e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ SET( DEFAULT_LIBRARY_INSTALL_DIR lib/ ) SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ ) SET( CMAKE_DEBUG_POSTFIX _debug ) SET( BUILD_SHARED_LIBS NO ) -SET( ECC_IMPL openssl ) # openssl or secp256k1 +SET( ECC_IMPL secp256k1 ) # openssl or secp256k1 set(platformBitness 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -37,6 +37,10 @@ SET(BOOST_COMPONENTS) LIST(APPEND BOOST_COMPONENTS thread date_time system filesystem program_options signals serialization chrono unit_test_framework context locale iostreams) SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) +IF( ECC_IMPL STREQUAL secp256k1 ) +SET( ECC_LIB secp256k1 ) +ENDIF( ECC_IMPL STREQUAL secp256k1 ) + IF( WIN32 ) MESSAGE(STATUS "Configuring fc to build on Win32") @@ -151,6 +155,7 @@ set( fc_sources src/crypto/sha512.cpp src/crypto/dh.cpp src/crypto/blowfish.cpp + src/crypto/elliptic_common.cpp src/crypto/elliptic_${ECC_IMPL}.cpp src/crypto/rand.cpp src/crypto/salsa20.cpp @@ -257,7 +262,7 @@ target_include_directories(fc ) #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) -target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries}) +target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries} ${ECC_LIB}) IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.(a|lib)$") IF(WIN32) diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp index 69c1bfb..0cb99ca 100644 --- a/include/fc/crypto/elliptic.hpp +++ b/include/fc/crypto/elliptic.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -43,7 +44,7 @@ namespace fc { public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical = true ); bool valid()const; - public_key mult( const fc::sha256& offset ); + public_key mult( const fc::sha256& offset )const; public_key add( const fc::sha256& offset )const; public_key( public_key&& pk ); @@ -61,10 +62,13 @@ namespace fc { /// Allows to convert current public key object into base58 number. std::string to_base58() const; + static std::string to_base58( const public_key_data &key ); static public_key from_base58( const std::string& b58 ); private: friend class private_key; + static public_key from_key_data( const public_key_data& v ); + static void is_canonical( const compact_signature& c ); fc::fwd my; }; @@ -123,6 +127,8 @@ namespace fc { } private: + private_key( EC_KEY* k ); + static fc::sha256 get_secret( const EC_KEY * const k ); fc::fwd my; }; } // namespace ecc diff --git a/src/crypto/elliptic_common.cpp b/src/crypto/elliptic_common.cpp new file mode 100644 index 0000000..e0c2659 --- /dev/null +++ b/src/crypto/elliptic_common.cpp @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +namespace fc { namespace ecc { + std::string public_key::to_base58( const public_key_data &key ) + { + uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; + assert(key.size() + sizeof(check) == 37); + array data; + memcpy(data.data, key.begin(), key.size()); + memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); + return fc::to_base58(data.begin(), data.size()); + } + + public_key public_key::from_base58( const std::string& b58 ) + { + array data; + size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); + FC_ASSERT( s == sizeof(data) ); + + public_key_data key; + uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; + FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); + memcpy( (char*)key.data, data.data, sizeof(key) ); + return from_key_data(key); + } + + void public_key::is_canonical( const compact_signature& c ) { + FC_ASSERT( !(c.data[1] & 0x80), "signature is not canonical" ); + FC_ASSERT( !(c.data[1] == 0 && !(c.data[2] & 0x80)), "signature is not canonical" ); + FC_ASSERT( !(c.data[33] & 0x80), "signature is not canonical" ); + FC_ASSERT( !(c.data[33] == 0 && !(c.data[34] & 0x80)), "signature is not canonical" ); + } + + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) + { + ssl_bignum z; + BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); + + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + + // secexp = (seed + z) % order + ssl_bignum secexp; + BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); + BN_add(secexp, secexp, z); + BN_mod(secexp, secexp, order, ctx); + + fc::sha256 secret; + assert(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); + auto shift = sizeof(secret) - BN_num_bytes(secexp); + BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); + return regenerate( secret ); + } + + fc::sha256 private_key::get_secret( const EC_KEY * const k ) + { + if( !k ) + { + return fc::sha256(); + } + + fc::sha256 sec; + const BIGNUM* bn = EC_KEY_get0_private_key(k); + if( bn == NULL ) + { + FC_THROW_EXCEPTION( exception, "get private key failed" ); + } + int nbytes = BN_num_bytes(bn); + BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); + return sec; + } + + private_key private_key::generate() + { + EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + if( !EC_KEY_generate_key( k ) ) + { + FC_THROW_EXCEPTION( exception, "ecc key generation error" ); + + } + + return private_key( k ); + } +} + void to_variant( const ecc::private_key& var, variant& vo ) + { + vo = var.get_secret(); + } + void from_variant( const variant& var, ecc::private_key& vo ) + { + fc::sha256 sec; + from_variant( var, sec ); + vo = ecc::private_key::regenerate(sec); + } + + void to_variant( const ecc::public_key& var, variant& vo ) + { + vo = var.serialize(); + } + void from_variant( const variant& var, ecc::public_key& vo ) + { + ecc::public_key_data dat; + from_variant( var, dat ); + vo = ecc::public_key(dat); + } +} diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 80ec0f7..e0a0d29 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -174,23 +174,11 @@ namespace fc { namespace ecc { return(ok); } -/* - public_key::public_key() - :my( new detail::public_key_impl() ) - { + public_key public_key::from_key_data( const public_key_data &data ) { + return public_key(data); } - public_key::public_key( fc::bigint pub_x, fc::bigint pub_y ) - :my( new detail::public_key_impl() ) - { - } - - public_key::~public_key() - { - } - */ - - public_key public_key::mult( const fc::sha256& digest ) + public_key public_key::mult( const fc::sha256& digest ) const { // get point from this public key const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); @@ -265,53 +253,12 @@ namespace fc { namespace ecc { std::string public_key::to_base58() const { public_key_data key = serialize(); - uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; - assert(key.size() + sizeof(check) == 37); - array data; - memcpy(data.data, key.begin(), key.size()); - memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); - return fc::to_base58(data.begin(), data.size()); - } - - public_key public_key::from_base58( const std::string& b58 ) - { - array data; - size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); - FC_ASSERT( s == sizeof(data) ); - - public_key_data key; - uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; - FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); - memcpy( (char*)key.data, data.data, sizeof(key) ); - return public_key(key); + return to_base58( key ); } private_key::private_key() {} - private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) - { - ssl_bignum z; - BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); - - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - bn_ctx ctx(BN_CTX_new()); - ssl_bignum order; - EC_GROUP_get_order(group, order, ctx); - - // secexp = (seed + z) % order - ssl_bignum secexp; - BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); - BN_add(secexp, secexp, z); - BN_mod(secexp, secexp, order, ctx); - - fc::sha256 secret; - assert(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); - auto shift = sizeof(secret) - BN_num_bytes(secexp); - BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); - return regenerate( secret ); - } - private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; @@ -330,45 +277,12 @@ namespace fc { namespace ecc { fc::sha256 private_key::get_secret()const { - if( !my->_key ) - { - return fc::sha256(); - } - - fc::sha256 sec; - const BIGNUM* bn = EC_KEY_get0_private_key(my->_key); - if( bn == NULL ) - { - FC_THROW_EXCEPTION( exception, "get private key failed" ); - } - int nbytes = BN_num_bytes(bn); - BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); - return sec; + return get_secret( my->_key ); } - private_key private_key::generate() + private_key::private_key( EC_KEY* k ) { - private_key self; - EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - self.my->_key = k; - if( !EC_KEY_generate_key( self.my->_key ) ) - { - FC_THROW_EXCEPTION( exception, "ecc key generation error" ); - - } - -#if 0 - = bigint( EC_KEY_get0_private_key( k ); - EC_POINT* pub = EC_KEY_get0_public_key( k ); - EC_GROUP* group = EC_KEY_get0_group( k ); - - EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr/*ctx*/ ); - - EC_KEY_free(k); -#endif - - return self; + my->_key = k; } // signature private_key::sign( const fc::sha256& digest )const @@ -422,9 +336,11 @@ namespace fc { namespace ecc { public_key::public_key() { } + public_key::~public_key() { } + public_key::public_key( const public_key_point_data& dat ) { const char* front = &dat.data[0]; @@ -493,10 +409,7 @@ namespace fc { namespace ecc { if( check_canonical ) { - FC_ASSERT( !(c.data[1] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[1] == 0 && !(c.data[2] & 0x80)), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] == 0 && !(c.data[34] & 0x80)), "signature is not canonical" ); + is_canonical( c ); } my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); @@ -595,18 +508,22 @@ namespace fc { namespace ecc { pk.my->_key = nullptr; return *this; } + public_key::public_key( const public_key& pk ) :my(pk.my) { } + public_key::public_key( public_key&& pk ) :my( fc::move( pk.my) ) { } + private_key::private_key( const private_key& pk ) :my(pk.my) { } + private_key::private_key( private_key&& pk ) :my( fc::move( pk.my) ) { @@ -640,29 +557,5 @@ namespace fc { namespace ecc { my->_key = EC_KEY_dup(pk.my->_key); return *this; } - } - void to_variant( const ecc::private_key& var, variant& vo ) - { - vo = var.get_secret(); - } - void from_variant( const variant& var, ecc::private_key& vo ) - { - fc::sha256 sec; - from_variant( var, sec ); - vo = ecc::private_key::regenerate(sec); - } - - void to_variant( const ecc::public_key& var, variant& vo ) - { - vo = var.serialize(); - } - void from_variant( const variant& var, ecc::public_key& vo ) - { - ecc::public_key_data dat; - from_variant( var, dat ); - vo = ecc::public_key(dat); - } - - } diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index fe11b50..a72eed6 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -8,472 +8,277 @@ #include #include +#include namespace fc { namespace ecc { namespace detail { + static void init_lib() { + static int init_s = 0; + static int init_o = init_openssl(); + if (!init_s) { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + init_s = 1; + } + } + + static public_key_data empty_key; + class public_key_impl { public: public_key_impl() - :_key(nullptr) { - static int init = init_openssl(); + init_lib(); } - ~public_key_impl() - { - if( _key != nullptr ) - { - EC_KEY_free(_key); - } - } public_key_impl( const public_key_impl& cpy ) { - _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + _key = cpy._key; } - EC_KEY* _key; + public_key_data _key; }; class private_key_impl { public: private_key_impl() - :_key(nullptr) { - static int init = init_openssl(); - } - ~private_key_impl() - { - if( _key != nullptr ) - { - EC_KEY_free(_key); - } + init_lib(); } private_key_impl( const private_key_impl& cpy ) { - _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + _key = cpy._key; } - EC_KEY* _key; + private_key_secret _key; }; } - static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) - { - if (*olen < SHA512_DIGEST_LENGTH) { - return NULL; - } - *olen = SHA512_DIGEST_LENGTH; - return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); +// static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) +// { +// if (*olen < SHA512_DIGEST_LENGTH) { +// return NULL; +// } +// *olen = SHA512_DIGEST_LENGTH; +// return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); +// } +// +// // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields +// // recid selects which key is recovered +// // if check is non-zero, additional checks are performed +// static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) +// { +// if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); +// +// int ret = 0; +// BN_CTX *ctx = NULL; +// +// BIGNUM *x = NULL; +// BIGNUM *e = NULL; +// BIGNUM *order = NULL; +// BIGNUM *sor = NULL; +// BIGNUM *eor = NULL; +// BIGNUM *field = NULL; +// EC_POINT *R = NULL; +// EC_POINT *O = NULL; +// EC_POINT *Q = NULL; +// BIGNUM *rr = NULL; +// BIGNUM *zero = NULL; +// int n = 0; +// int i = recid / 2; +// +// const EC_GROUP *group = EC_KEY_get0_group(eckey); +// if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } +// BN_CTX_start(ctx); +// order = BN_CTX_get(ctx); +// if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } +// x = BN_CTX_get(ctx); +// if (!BN_copy(x, order)) { ret=-1; goto err; } +// if (!BN_mul_word(x, i)) { ret=-1; goto err; } +// if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } +// field = BN_CTX_get(ctx); +// if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } +// if (BN_cmp(x, field) >= 0) { ret=0; goto err; } +// if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } +// if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } +// if (check) +// { +// if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } +// if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } +// if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } +// } +// if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } +// n = EC_GROUP_get_degree(group); +// e = BN_CTX_get(ctx); +// if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } +// if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); +// zero = BN_CTX_get(ctx); +// if (!BN_zero(zero)) { ret=-1; goto err; } +// if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } +// rr = BN_CTX_get(ctx); +// if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } +// sor = BN_CTX_get(ctx); +// if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } +// eor = BN_CTX_get(ctx); +// if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } +// if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } +// if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } +// +// ret = 1; +// +// err: +// if (ctx) { +// BN_CTX_end(ctx); +// BN_CTX_free(ctx); +// } +// if (R != NULL) EC_POINT_free(R); +// if (O != NULL) EC_POINT_free(O); +// if (Q != NULL) EC_POINT_free(Q); +// return ret; +// } +// +// +// int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) +// { +// int ok = 0; +// BN_CTX *ctx = NULL; +// EC_POINT *pub_key = NULL; +// +// if (!eckey) return 0; +// +// const EC_GROUP *group = EC_KEY_get0_group(eckey); +// +// if ((ctx = BN_CTX_new()) == NULL) +// goto err; +// +// pub_key = EC_POINT_new(group); +// +// if (pub_key == NULL) +// goto err; +// +// if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) +// goto err; +// +// EC_KEY_set_private_key(eckey,priv_key); +// EC_KEY_set_public_key(eckey,pub_key); +// +// ok = 1; +// +// err: +// +// if (pub_key) EC_POINT_free(pub_key); +// if (ctx != NULL) BN_CTX_free(ctx); +// +// return(ok); +// } + + public_key public_key::from_key_data( const public_key_data &data ) { + return public_key(data); } - // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields - // recid selects which key is recovered - // if check is non-zero, additional checks are performed - static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) + public_key public_key::mult( const fc::sha256& digest )const { - if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); - - int ret = 0; - BN_CTX *ctx = NULL; - - BIGNUM *x = NULL; - BIGNUM *e = NULL; - BIGNUM *order = NULL; - BIGNUM *sor = NULL; - BIGNUM *eor = NULL; - BIGNUM *field = NULL; - EC_POINT *R = NULL; - EC_POINT *O = NULL; - EC_POINT *Q = NULL; - BIGNUM *rr = NULL; - BIGNUM *zero = NULL; - int n = 0; - int i = recid / 2; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } - BN_CTX_start(ctx); - order = BN_CTX_get(ctx); - if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } - x = BN_CTX_get(ctx); - if (!BN_copy(x, order)) { ret=-1; goto err; } - if (!BN_mul_word(x, i)) { ret=-1; goto err; } - if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } - field = BN_CTX_get(ctx); - if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } - if (BN_cmp(x, field) >= 0) { ret=0; goto err; } - if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } - if (check) - { - if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } - if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } - } - if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - n = EC_GROUP_get_degree(group); - e = BN_CTX_get(ctx); - if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } - if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); - zero = BN_CTX_get(ctx); - if (!BN_zero(zero)) { ret=-1; goto err; } - if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } - rr = BN_CTX_get(ctx); - if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } - sor = BN_CTX_get(ctx); - if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } - eor = BN_CTX_get(ctx); - if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } - if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } - if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } - - ret = 1; - - err: - if (ctx) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - if (R != NULL) EC_POINT_free(R); - if (O != NULL) EC_POINT_free(O); - if (Q != NULL) EC_POINT_free(Q); - return ret; + public_key_data new_key; + memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + return public_key( new_key ); } - - int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) - { - int ok = 0; - BN_CTX *ctx = NULL; - EC_POINT *pub_key = NULL; - - if (!eckey) return 0; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - - if ((ctx = BN_CTX_new()) == NULL) - goto err; - - pub_key = EC_POINT_new(group); - - if (pub_key == NULL) - goto err; - - if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) - goto err; - - EC_KEY_set_private_key(eckey,priv_key); - EC_KEY_set_public_key(eckey,pub_key); - - ok = 1; - - err: - - if (pub_key) EC_POINT_free(pub_key); - if (ctx != NULL) BN_CTX_free(ctx); - - return(ok); - } - -/* - public_key::public_key() - :my( new detail::public_key_impl() ) - { - } - - public_key::public_key( fc::bigint pub_x, fc::bigint pub_y ) - :my( new detail::public_key_impl() ) - { - } - - public_key::~public_key() - { - } - */ - - public_key public_key::mult( const fc::sha256& digest ) - { - // get point from this public key - const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - - ssl_bignum z; - BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); - - // multiply by digest - ssl_bignum one; - BN_one(one); - bn_ctx ctx(BN_CTX_new()); - - ec_point result(EC_POINT_new(group)); - EC_POINT_mul(group, result, z, master_pub, one, ctx); - - public_key rtn; - rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - EC_KEY_set_public_key(rtn.my->_key,result); - - return rtn; - } bool public_key::valid()const { - return my->_key != nullptr; + return my->_key != detail::empty_key; } + public_key public_key::add( const fc::sha256& digest )const { - try { - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - bn_ctx ctx(BN_CTX_new()); - - fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); - - ssl_bignum order; - EC_GROUP_get_order(group, order, ctx); - if( digest_bi > fc::bigint(order) ) - { - FC_THROW_EXCEPTION( exception, "digest > group order" ); - } - - - public_key digest_key = private_key::regenerate(digest).get_public_key(); - const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); - - // get point from this public key - const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); - - ssl_bignum z; - BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); - - // multiply by digest - ssl_bignum one; - BN_one(one); - - ec_point result(EC_POINT_new(group)); - EC_POINT_add(group, result, digest_point, master_pub, ctx); - - if (EC_POINT_is_at_infinity(group, result)) - { - FC_THROW_EXCEPTION( exception, "point at infinity" ); - } - - - public_key rtn; - rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - EC_KEY_set_public_key(rtn.my->_key,result); - return rtn; - } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + public_key_data new_key; + memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + return public_key( new_key ); } std::string public_key::to_base58() const { - public_key_data key = serialize(); - uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; - assert(key.size() + sizeof(check) == 37); - array data; - memcpy(data.data, key.begin(), key.size()); - memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); - return fc::to_base58(data.begin(), data.size()); - } - - public_key public_key::from_base58( const std::string& b58 ) - { - array data; - size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); - FC_ASSERT( s == sizeof(data) ); - - public_key_data key; - uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; - FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); - memcpy( (char*)key.data, data.data, sizeof(key) ); - return public_key(key); + return to_base58( my->_key ); } private_key::private_key() {} - private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) - { - ssl_bignum z; - BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); - - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - bn_ctx ctx(BN_CTX_new()); - ssl_bignum order; - EC_GROUP_get_order(group, order, ctx); - - // secexp = (seed + z) % order - ssl_bignum secexp; - BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); - BN_add(secexp, secexp, z); - BN_mod(secexp, secexp, order, ctx); - - fc::sha256 secret; - assert(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); - auto shift = sizeof(secret) - BN_num_bytes(secexp); - BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); - return regenerate( secret ); - } - private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; - self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - - ssl_bignum bn; - BN_bin2bn( (const unsigned char*)&secret, 32, bn ); - - if( !EC_KEY_regenerate_key(self.my->_key,bn) ) - { - FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); - } + self.my->_key = secret; return self; } fc::sha256 private_key::get_secret()const { - if( !my->_key ) - { - return fc::sha256(); - } - - fc::sha256 sec; - const BIGNUM* bn = EC_KEY_get0_private_key(my->_key); - if( bn == NULL ) - { - FC_THROW_EXCEPTION( exception, "get private key failed" ); - } - int nbytes = BN_num_bytes(bn); - BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); - return sec; + return my->_key; } - private_key private_key::generate() + private_key::private_key( EC_KEY* k ) { - private_key self; - EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - self.my->_key = k; - if( !EC_KEY_generate_key( self.my->_key ) ) - { - FC_THROW_EXCEPTION( exception, "ecc key generation error" ); - - } - -#if 0 - = bigint( EC_KEY_get0_private_key( k ); - EC_POINT* pub = EC_KEY_get0_public_key( k ); - EC_GROUP* group = EC_KEY_get0_group( k ); - - EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr/*ctx*/ ); - + my->_key = get_secret( k ); EC_KEY_free(k); -#endif - - return self; - } - - signature private_key::sign( const fc::sha256& digest )const - { - unsigned int buf_len = ECDSA_size(my->_key); -// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); - signature sig; - assert( buf_len == sizeof(sig) ); - - if( !ECDSA_sign( 0, - (const unsigned char*)&digest, sizeof(digest), - (unsigned char*)&sig, &buf_len, my->_key ) ) - { - FC_THROW_EXCEPTION( exception, "signing error" ); - } - - - return sig; - } - bool public_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) - { - return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); } public_key_data public_key::serialize()const { - public_key_data dat; - if( !my->_key ) return dat; - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); - /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ - /*assert( nbytes == 33 )*/ - char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); - return dat; - /* - EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); - EC_GROUP* group = EC_KEY_get0_group( my->_key ); - EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); - */ + return my->_key; } public_key_point_data public_key::serialize_ecc_point()const { public_key_point_data dat; - if( !my->_key ) return dat; - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); - char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + memcpy( dat.begin(), my->_key.begin(), my->_key.size() ); + unsigned int pk_len = my->_key.size(); + FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); + FC_ASSERT( pk_len == dat.size() ); return dat; } public_key::public_key() { } + public_key::~public_key() { } + + // FIXME public_key::public_key( const public_key_point_data& dat ) { const char* front = &dat.data[0]; if( *front == 0 ){} else { - /*my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); */ - my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); - if( !my->_key ) - { - FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); - } - } - } - public_key::public_key( const public_key_data& dat ) - { - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); - if( !my->_key ) - { - FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); - } +// my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); +// if( !my->_key ) +// { +// FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); +// } } } - bool private_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) + public_key::public_key( const public_key_data& dat ) { - return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); + my->_key = dat; } public_key private_key::get_public_key()const { public_key pub; - pub.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - EC_KEY_set_public_key( pub.my->_key, EC_KEY_get0_public_key( my->_key ) ); + unsigned int pk_len; + FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.my->_key.begin(), (int*) &pk_len, (unsigned char*) my->_key.data(), 1 ) ); + FC_ASSERT( pk_len == pub.my->_key.size() ); return pub; } - + // FIXME fc::sha512 private_key::get_shared_secret( const public_key& other )const { - FC_ASSERT( my->_key != nullptr ); - FC_ASSERT( other.my->_key != nullptr ); +// FC_ASSERT( my->_key != nullptr ); +// FC_ASSERT( other.my->_key != nullptr ); fc::sha512 buf; - ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); +// ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); return buf; } @@ -487,112 +292,26 @@ namespace fc { namespace ecc { if (nV<27 || nV>=35) FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); - ECDSA_SIG *sig = ECDSA_SIG_new(); - BN_bin2bn(&c.data[1],32,sig->r); - BN_bin2bn(&c.data[33],32,sig->s); - if( check_canonical ) { - FC_ASSERT( !(c.data[1] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[1] == 0 && !(c.data[2] & 0x80)), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] == 0 && !(c.data[34] & 0x80)), "signature is not canonical" ); + is_canonical( c ); } - my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); - - if (nV >= 31) - { - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); - nV -= 4; -// fprintf( stderr, "compressed\n" ); - } - - if (ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) - { - ECDSA_SIG_free(sig); - return; - } - ECDSA_SIG_free(sig); - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + unsigned int pk_len; + FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key.begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( pk_len == my->_key.size() ); } compact_signature private_key::sign_compact( const fc::sha256& digest )const { - try { - FC_ASSERT( my->_key != nullptr ); - auto my_pub_key = get_public_key().serialize(); // just for good measure - //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - while( true ) - { - ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - - if (sig==nullptr) - FC_THROW_EXCEPTION( exception, "Unable to sign" ); - - compact_signature csig; - // memset( csig.data, 0, sizeof(csig) ); - - int nBitsR = BN_num_bits(sig->r); - int nBitsS = BN_num_bits(sig->s); - if (nBitsR <= 256 && nBitsS <= 256) - { - int nRecId = -1; - for (int i=0; i<4; i++) - { - public_key keyRec; - keyRec.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if (ECDSA_SIG_recover_key_GFp(keyRec.my->_key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) - { - if (keyRec.serialize() == my_pub_key ) - { - nRecId = i; - break; - } - } - } - - if (nRecId == -1) - { - FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); - } - unsigned char* result = nullptr; - auto bytes = i2d_ECDSA_SIG( sig, &result ); - auto lenR = result[3]; - auto lenS = result[5+lenR]; - //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); - if( lenR != 32 ) { free(result); continue; } - if( lenS != 32 ) { free(result); continue; } - //idump( (33-(nBitsR+7)/8) ); - //idump( (65-(nBitsS+7)/8) ); - //idump( (sizeof(csig) ) ); - memcpy( &csig.data[1], &result[4], lenR ); - memcpy( &csig.data[33], &result[6+lenR], lenS ); - //idump( (csig.data[33]) ); - //idump( (csig.data[1]) ); - free(result); - //idump( (nRecId) ); - csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); - /* - idump( (csig) ); - auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); - auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); - idump( (rlen)(slen) ); - */ - } - return csig; - } // while true - } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); + compact_signature result; + FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin(), (unsigned char*) my->_key.data(), NULL, NULL, NULL )); + return result; } private_key& private_key::operator=( private_key&& pk ) { - if( my->_key ) - { - EC_KEY_free(my->_key); - } my->_key = pk.my->_key; - pk.my->_key = nullptr; return *this; } public_key::public_key( const public_key& pk ) @@ -614,55 +333,18 @@ namespace fc { namespace ecc { public_key& public_key::operator=( public_key&& pk ) { - if( my->_key ) - { - EC_KEY_free(my->_key); - } my->_key = pk.my->_key; - pk.my->_key = nullptr; return *this; } public_key& public_key::operator=( const public_key& pk ) { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = EC_KEY_dup(pk.my->_key); + my->_key = pk.my->_key; return *this; } private_key& private_key::operator=( const private_key& pk ) { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = EC_KEY_dup(pk.my->_key); + my->_key = pk.my->_key; return *this; } - } - void to_variant( const ecc::private_key& var, variant& vo ) - { - vo = var.get_secret(); - } - void from_variant( const variant& var, ecc::private_key& vo ) - { - fc::sha256 sec; - from_variant( var, sec ); - vo = ecc::private_key::regenerate(sec); - } - - void to_variant( const ecc::public_key& var, variant& vo ) - { - vo = var.serialize(); - } - void from_variant( const variant& var, ecc::public_key& vo ) - { - ecc::public_key_data dat; - from_variant( var, dat ); - vo = ecc::public_key(dat); - } - - } From 7b15098f3a1011adeff3212a99b08e753953a220 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Mon, 9 Mar 2015 10:30:34 +0100 Subject: [PATCH 19/54] Woot! --- include/fc/crypto/elliptic.hpp | 7 +- src/crypto/elliptic_common.cpp | 10 +- src/crypto/elliptic_openssl.cpp | 59 +++++---- src/crypto/elliptic_secp256k1.cpp | 207 +++++++++++++++++++++++------- tests/ecc_test.cpp | 20 ++- 5 files changed, 215 insertions(+), 88 deletions(-) diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp index 0cb99ca..7de417f 100644 --- a/include/fc/crypto/elliptic.hpp +++ b/include/fc/crypto/elliptic.hpp @@ -44,7 +44,10 @@ namespace fc { public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical = true ); bool valid()const; - public_key mult( const fc::sha256& offset )const; + /** Computes new pubkey = generator * offset + old pubkey ?! */ +// public_key mult( const fc::sha256& offset )const; + /** Computes new pubkey = regenerate(offset).pubkey + old pubkey + * = offset * G + 1 * old pubkey ?! */ public_key add( const fc::sha256& offset )const; public_key( public_key&& pk ); @@ -68,7 +71,7 @@ namespace fc { private: friend class private_key; static public_key from_key_data( const public_key_data& v ); - static void is_canonical( const compact_signature& c ); + static bool is_canonical( const compact_signature& c ); fc::fwd my; }; diff --git a/src/crypto/elliptic_common.cpp b/src/crypto/elliptic_common.cpp index e0c2659..66d19d7 100644 --- a/src/crypto/elliptic_common.cpp +++ b/src/crypto/elliptic_common.cpp @@ -33,11 +33,11 @@ namespace fc { namespace ecc { return from_key_data(key); } - void public_key::is_canonical( const compact_signature& c ) { - FC_ASSERT( !(c.data[1] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[1] == 0 && !(c.data[2] & 0x80)), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] & 0x80), "signature is not canonical" ); - FC_ASSERT( !(c.data[33] == 0 && !(c.data[34] & 0x80)), "signature is not canonical" ); + bool public_key::is_canonical( const compact_signature& c ) { + return !(c.data[1] & 0x80) + && !(c.data[1] == 0 && !(c.data[2] & 0x80)) + && !(c.data[33] & 0x80) + && !(c.data[33] == 0 && !(c.data[34] & 0x80)); } private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index e0a0d29..244372d 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -178,29 +178,32 @@ namespace fc { namespace ecc { return public_key(data); } - public_key public_key::mult( const fc::sha256& digest ) const - { - // get point from this public key - const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - - ssl_bignum z; - BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); - - // multiply by digest - ssl_bignum one; - BN_one(one); - bn_ctx ctx(BN_CTX_new()); - - ec_point result(EC_POINT_new(group)); - EC_POINT_mul(group, result, z, master_pub, one, ctx); - - public_key rtn; - rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - EC_KEY_set_public_key(rtn.my->_key,result); - - return rtn; - } + /* WARNING! This implementation is broken, it is actually equivalent to + * public_key::add()! + */ +// public_key public_key::mult( const fc::sha256& digest ) const +// { +// // get point from this public key +// const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); +// ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); +// +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); +// +// // multiply by digest +// ssl_bignum one; +// BN_one(one); +// bn_ctx ctx(BN_CTX_new()); +// +// ec_point result(EC_POINT_new(group)); +// EC_POINT_mul(group, result, z, master_pub, one, ctx); +// +// public_key rtn; +// rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); +// EC_KEY_set_public_key(rtn.my->_key,result); +// +// return rtn; +// } bool public_key::valid()const { return my->_key != nullptr; @@ -227,12 +230,12 @@ namespace fc { namespace ecc { // get point from this public key const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); - ssl_bignum z; - BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); // multiply by digest - ssl_bignum one; - BN_one(one); +// ssl_bignum one; +// BN_one(one); ec_point result(EC_POINT_new(group)); EC_POINT_add(group, result, digest_point, master_pub, ctx); @@ -409,7 +412,7 @@ namespace fc { namespace ecc { if( check_canonical ) { - is_canonical( c ); + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); } my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index a72eed6..1a5d8c5 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -27,29 +27,124 @@ namespace fc { namespace ecc { class public_key_impl { public: - public_key_impl() + public_key_impl() : _key(nullptr) { init_lib(); } public_key_impl( const public_key_impl& cpy ) { - _key = cpy._key; + init_lib(); + _key = nullptr; + *this = cpy; } - public_key_data _key; + + public_key_impl( public_key_impl&& cpy ) + { + init_lib(); + _key = nullptr; + *this = cpy; + } + + ~public_key_impl() + { + if( _key != nullptr ) + { + delete _key; + _key = nullptr; + } + } + + public_key_impl& operator=( const public_key_impl& pk ) + { + if (pk._key == nullptr) + { + if (_key != nullptr) + { + delete _key; + _key = nullptr; + } + } else if ( _key == nullptr ) { + _key = new public_key_data(*pk._key); + } else { + *_key = *pk._key; + } + return *this; + } + + public_key_impl& operator=( public_key_impl&& pk ) + { + if (_key != nullptr) + { + delete _key; + } + _key = pk._key; + pk._key = nullptr; + return *this; + } + + public_key_data *_key; }; class private_key_impl { public: - private_key_impl() + private_key_impl() : _key(nullptr) { init_lib(); } + private_key_impl( const private_key_impl& cpy ) { - _key = cpy._key; + init_lib(); + _key = nullptr; + *this = cpy; } - private_key_secret _key; + + private_key_impl( private_key_impl&& cpy ) + { + init_lib(); + _key = nullptr; + *this = cpy; + } + + ~private_key_impl() + { + if( _key != nullptr ) + { + delete _key; + _key = nullptr; + } + } + + private_key_impl& operator=( const private_key_impl& pk ) + { + if (pk._key == nullptr) + { + if (_key != nullptr) + { + delete _key; + _key = nullptr; + } + } else if ( _key == nullptr ) { + _key = new private_key_secret(*pk._key); + } else { + *_key = *pk._key; + } + return *this; + } + + private_key_impl& operator=( private_key_impl&& pk ) + { + if (_key != nullptr) + { + delete _key; + } + _key = pk._key; + pk._key = nullptr; + return *this; + } + + private_key_secret *_key; }; } // static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) @@ -174,30 +269,33 @@ namespace fc { namespace ecc { return public_key(data); } - public_key public_key::mult( const fc::sha256& digest )const - { - public_key_data new_key; - memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); - FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); - return public_key( new_key ); - } +// public_key public_key::mult( const fc::sha256& digest )const +// { +// FC_ASSERT( my->_key != nullptr ); +// public_key_data new_key; +// memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); +// FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); +// return public_key( new_key ); +// } bool public_key::valid()const { - return my->_key != detail::empty_key; + return my->_key != nullptr; } public_key public_key::add( const fc::sha256& digest )const { + FC_ASSERT( my->_key != nullptr ); public_key_data new_key; - memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); + memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); return public_key( new_key ); } std::string public_key::to_base58() const { - return to_base58( my->_key ); + FC_ASSERT( my->_key != nullptr ); + return to_base58( *my->_key ); } private_key::private_key() @@ -206,30 +304,33 @@ namespace fc { namespace ecc { private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; - self.my->_key = secret; + self.my->_key = new private_key_secret(secret); return self; } fc::sha256 private_key::get_secret()const { - return my->_key; + FC_ASSERT( my->_key != nullptr ); + return *my->_key; } private_key::private_key( EC_KEY* k ) { - my->_key = get_secret( k ); + my->_key = new private_key_secret( get_secret( k ) ); EC_KEY_free(k); } public_key_data public_key::serialize()const { - return my->_key; + FC_ASSERT( my->_key != nullptr ); + return *my->_key; } public_key_point_data public_key::serialize_ecc_point()const { + FC_ASSERT( my->_key != nullptr ); public_key_point_data dat; - memcpy( dat.begin(), my->_key.begin(), my->_key.size() ); - unsigned int pk_len = my->_key.size(); + unsigned int pk_len = my->_key->size(); + memcpy( dat.begin(), my->_key->begin(), pk_len ); FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); FC_ASSERT( pk_len == dat.size() ); return dat; @@ -243,43 +344,44 @@ namespace fc { namespace ecc { { } - // FIXME public_key::public_key( const public_key_point_data& dat ) { const char* front = &dat.data[0]; if( *front == 0 ){} else { -// my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); -// if( !my->_key ) -// { -// FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); -// } + EC_KEY *key = o2i_ECPublicKey( nullptr, (const unsigned char**)&front, sizeof(dat) ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + my->_key = new public_key_data(); + i2o_ECPublicKey( key, (unsigned char**)&my->_key->data ); + EC_KEY_free( key ); } } public_key::public_key( const public_key_data& dat ) { - my->_key = dat; + my->_key = new public_key_data(dat); } public_key private_key::get_public_key()const { - public_key pub; + FC_ASSERT( my->_key != nullptr ); + public_key_data pub; unsigned int pk_len; - FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.my->_key.begin(), (int*) &pk_len, (unsigned char*) my->_key.data(), 1 ) ); - FC_ASSERT( pk_len == pub.my->_key.size() ); - return pub; + FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key->data(), 1 ) ); + FC_ASSERT( pk_len == pub.size() ); + return public_key(pub); } - // FIXME fc::sha512 private_key::get_shared_secret( const public_key& other )const { -// FC_ASSERT( my->_key != nullptr ); -// FC_ASSERT( other.my->_key != nullptr ); - fc::sha512 buf; + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + public_key_data pub(*other.my->_key); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key->data() ) ); // ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); - return buf; + return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } private_key::~private_key() @@ -294,24 +396,31 @@ namespace fc { namespace ecc { if( check_canonical ) { - is_canonical( c ); + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); } + my->_key = new public_key_data(); unsigned int pk_len; - FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key.begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); - FC_ASSERT( pk_len == my->_key.size() ); + FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( pk_len == my->_key->size() ); } compact_signature private_key::sign_compact( const fc::sha256& digest )const { + FC_ASSERT( my->_key != nullptr ); compact_signature result; - FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin(), (unsigned char*) my->_key.data(), NULL, NULL, NULL )); + int recid; + do + { + FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), NULL, NULL, &recid )); + } while( !public_key::is_canonical( result ) ); + result.begin()[0] = 27 + 4 + recid; return result; } - private_key& private_key::operator=( private_key&& pk ) + private_key& private_key::operator=( private_key&& pk ) { - my->_key = pk.my->_key; + my = std::move(pk.my); return *this; } public_key::public_key( const public_key& pk ) @@ -319,7 +428,7 @@ namespace fc { namespace ecc { { } public_key::public_key( public_key&& pk ) - :my( fc::move( pk.my) ) + :my( std::move(pk.my) ) { } private_key::private_key( const private_key& pk ) @@ -327,23 +436,23 @@ namespace fc { namespace ecc { { } private_key::private_key( private_key&& pk ) - :my( fc::move( pk.my) ) + :my( std::move( pk.my) ) { } public_key& public_key::operator=( public_key&& pk ) { - my->_key = pk.my->_key; + my = std::move(pk.my); return *this; } public_key& public_key::operator=( const public_key& pk ) { - my->_key = pk.my->_key; + my = pk.my; return *this; } private_key& private_key::operator=( const private_key& pk ) { - my->_key = pk.my->_key; + my = pk.my; return *this; } } diff --git a/tests/ecc_test.cpp b/tests/ecc_test.cpp index 99c728f..5492d61 100644 --- a/tests/ecc_test.cpp +++ b/tests/ecc_test.cpp @@ -34,9 +34,13 @@ static void interop_do(const fc::ecc::public_key_point_data &data) { interop_do(data.begin(), data.size()); } -//static void interop_do(const fc::ecc::signature &data) { -// interop_do(data.begin(), data.size()); -//} +static void interop_do(const std::string &data) { + interop_do(data.c_str(), data.length()); +} + +static void interop_do(const fc::sha512 &data) { + interop_do(data.data(), 64); +} static void interop_do(fc::ecc::compact_signature &data) { if (write_mode) { @@ -79,12 +83,20 @@ int main( int argc, char** argv ) pass += "1"; fc::sha256 h2 = fc::sha256::hash( pass.c_str(), pass.size() ); - fc::ecc::public_key pub1 = pub.mult( h2 ); + fc::ecc::public_key pub1 = pub.add( h2 ); interop_do(pub1.serialize()); interop_do(pub1.serialize_ecc_point()); fc::ecc::private_key priv1 = fc::ecc::private_key::generate_from_seed(h, h2); interop_do(priv1.get_secret()); + std::string b58 = pub1.to_base58(); + interop_do(b58); + fc::ecc::public_key pub2 = fc::ecc::public_key::from_base58(b58); + FC_ASSERT( pub1 == pub2 ); + + fc::sha512 shared = priv1.get_shared_secret( pub ); + interop_do(shared); + auto sig = priv.sign_compact( h ); interop_do(sig); auto recover = fc::ecc::public_key( sig, h ); From 20230b761e21ef2e43df639dc962b6a9894ea6f4 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Mon, 9 Mar 2015 11:18:33 +0100 Subject: [PATCH 20/54] Added documentation + automated interop test --- CMakeLists.txt | 6 ++--- README-ecc.md | 55 ++++++++++++++++++++++++++++++++++++++++++++ tests/ecc-interop.sh | 35 ++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 README-ecc.md create mode 100755 tests/ecc-interop.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 6adb05e..e2bbb22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ SET( DEFAULT_LIBRARY_INSTALL_DIR lib/ ) SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ ) SET( CMAKE_DEBUG_POSTFIX _debug ) SET( BUILD_SHARED_LIBS NO ) -SET( ECC_IMPL secp256k1 ) # openssl or secp256k1 +SET( ECC_IMPL openssl CACHE STRING "openssl or secp256k1" ) set(platformBitness 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -261,8 +261,8 @@ target_include_directories(fc ${CMAKE_CURRENT_SOURCE_DIR}/vendor/websocketpp ) -#target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) -target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries} ${ECC_LIB}) +#target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${ECC_LIB} ) +target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries} ${ECC_LIB} ) IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.(a|lib)$") IF(WIN32) diff --git a/README-ecc.md b/README-ecc.md new file mode 100644 index 0000000..8c1046a --- /dev/null +++ b/README-ecc.md @@ -0,0 +1,55 @@ +ECC Support +=========== + +include/fc/crypto/elliptic.hpp defines an interface for some cryptographic +wrapper classes handling elliptic curve cryptography. + +Two implementations of this interface exist. One is based on OpenSSL, the +other is based on libsecp256k1 (see https://github.com/bitcoin/secp256k1 ). +The implementation to be used is selected at compile time using the +cmake variable "ECC_IMPL". It can take two values, openssl or secp256k1 . +The default is "openssl". The alternative can be configured when invoking +cmake, for example + +cmake -D ECC_IMPL=secp256k1 . + +If secp256k1 is chosen, the secp256k1 library and its include file must +already be installed in the appropriate library / include directories on +your system. + + +Testing +------- + +Type "make ecc_test" to build the ecc_test executable from tests/ecc_test.cpp +with the currently configured ECC implementation. + +ecc_test expects two arguments: + +ecc_test + + is a somewhat arbitrary password used for testing. + + is a data file containing intermediate test results. +If the file does not exist, it will be created and intermediate results from +the current ECC backend are written to it. +If the file does exist, intermediate results from the current ECC backend +are compared with the file contents. + +For a full round of interoperability testing, you need to do this: + +1. Build ecc_test with openssl backend. +2. Run "ecc_test test ecc.interop.openssl". +3. Run "ecc_test test ecc.interop.openssl" again, testing openssl against + itself. +4. Build ecc_test with secp256k1 backend. +5. Run "ecc_test test ecc.interop.secp256k1". +6. Run "ecc_test test ecc.interop.secp256k1" again, testing secp256k1 against + itself. +7. Run "ecc_test test ecc.interop.openssl", testing secp256k1 against openssl. +8. Build ecc_test with openssl backend. +9. Run "ecc_test test ecc.interop.secp256k1", testing openssl against secp256k1. + +None of the test runs should produce any output. The above steps are scripted +in tests//ecc-interop.sh . + diff --git a/tests/ecc-interop.sh b/tests/ecc-interop.sh new file mode 100755 index 0000000..50ff0c7 --- /dev/null +++ b/tests/ecc-interop.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +#TIME=time + +cd "`dirname $0`"/.. + +echo Building ecc_test with openssl... +( +cmake -D ECC_IMPL=openssl . +make ecc_test +mv ecc_test ecc_test.openssl +) >/dev/null 2>&1 + +echo Building ecc_test with secp256k1... +( +cmake -D ECC_IMPL=secp256k1 . +make ecc_test +mv ecc_test ecc_test.secp256k1 +) >/dev/null 2>&1 + +run () { + echo "Running ecc_test.$1 test ecc.interop.$1 ..." + $TIME "./ecc_test.$1" test "ecc.interop.$1" +} + +run openssl +run openssl +run secp256k1 +run secp256k1 +run secp256k1 +run openssl + +echo Done. + +rm -f ecc_test.openssl ecc_test.secp256k1 ecc.interop.openssl ecc.interop.secp256k1 From 55c5773a462259bf6ba37eb8c3426bc48f9a632b Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 11:34:14 +0100 Subject: [PATCH 21/54] Started refactoring --- CMakeLists.txt | 1 - src/crypto/{elliptic_common.cpp => _elliptic_common.cpp} | 0 src/crypto/elliptic_openssl.cpp | 2 ++ src/crypto/elliptic_secp256k1.cpp | 4 ++-- 4 files changed, 4 insertions(+), 3 deletions(-) rename src/crypto/{elliptic_common.cpp => _elliptic_common.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2bbb22..2329374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,7 +155,6 @@ set( fc_sources src/crypto/sha512.cpp src/crypto/dh.cpp src/crypto/blowfish.cpp - src/crypto/elliptic_common.cpp src/crypto/elliptic_${ECC_IMPL}.cpp src/crypto/rand.cpp src/crypto/salsa20.cpp diff --git a/src/crypto/elliptic_common.cpp b/src/crypto/_elliptic_common.cpp similarity index 100% rename from src/crypto/elliptic_common.cpp rename to src/crypto/_elliptic_common.cpp diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 244372d..eef3d9e 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -562,3 +562,5 @@ namespace fc { namespace ecc { } } } + +#include "_elliptic_common.cpp" diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 1a5d8c5..73ecf24 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -22,8 +22,6 @@ namespace fc { namespace ecc { } } - static public_key_data empty_key; - class public_key_impl { public: @@ -457,3 +455,5 @@ namespace fc { namespace ecc { } } } + +#include "_elliptic_common.cpp" From 414617d8e3532a0766ba1a65502e6ebb1b431ad7 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 14:47:53 +0100 Subject: [PATCH 22/54] Refactoring, step 2 --- src/crypto/_elliptic_common.cpp | 83 +++++--- src/crypto/_elliptic_impl.cpp | 107 ++++++++++ src/crypto/elliptic_openssl.cpp | 160 +++++---------- src/crypto/elliptic_secp256k1.cpp | 322 +++--------------------------- 4 files changed, 241 insertions(+), 431 deletions(-) create mode 100644 src/crypto/_elliptic_impl.cpp diff --git a/src/crypto/_elliptic_common.cpp b/src/crypto/_elliptic_common.cpp index 66d19d7..12c8c63 100644 --- a/src/crypto/_elliptic_common.cpp +++ b/src/crypto/_elliptic_common.cpp @@ -1,15 +1,17 @@ -#include - -#include -#include - -#include -#include -#include - -#include - namespace fc { namespace ecc { + public_key::public_key() {} + + public_key::~public_key() {} + + public_key::public_key( const public_key& pk ) : my( pk.my ) {} + + public_key::public_key( public_key&& pk ) : my( std::move(pk.my) ) {} + + bool public_key::valid()const + { + return my->_key != nullptr; + } + std::string public_key::to_base58( const public_key_data &key ) { uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; @@ -40,6 +42,14 @@ namespace fc { namespace ecc { && !(c.data[33] == 0 && !(c.data[34] & 0x80)); } + private_key::private_key() {} + + private_key::~private_key() {} + + private_key::private_key( const private_key& pk ) : my(pk.my) {} + + private_key::private_key( private_key&& pk ) : my( std::move( pk.my) ) {} + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) { ssl_bignum z; @@ -93,26 +103,53 @@ namespace fc { namespace ecc { return private_key( k ); } + + private_key& private_key::operator=( private_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + public_key& public_key::operator=( public_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + public_key& public_key::operator=( const public_key& pk ) + { + my = pk.my; + return *this; + } + + private_key& private_key::operator=( const private_key& pk ) + { + my = pk.my; + return *this; + } } - void to_variant( const ecc::private_key& var, variant& vo ) - { + +void to_variant( const ecc::private_key& var, variant& vo ) +{ vo = var.get_secret(); - } - void from_variant( const variant& var, ecc::private_key& vo ) - { +} + +void from_variant( const variant& var, ecc::private_key& vo ) +{ fc::sha256 sec; from_variant( var, sec ); vo = ecc::private_key::regenerate(sec); - } +} - void to_variant( const ecc::public_key& var, variant& vo ) - { +void to_variant( const ecc::public_key& var, variant& vo ) +{ vo = var.serialize(); - } - void from_variant( const variant& var, ecc::public_key& vo ) - { +} + +void from_variant( const variant& var, ecc::public_key& vo ) +{ ecc::public_key_data dat; from_variant( var, dat ); vo = ecc::public_key(dat); - } +} } diff --git a/src/crypto/_elliptic_impl.cpp b/src/crypto/_elliptic_impl.cpp new file mode 100644 index 0000000..916e0a1 --- /dev/null +++ b/src/crypto/_elliptic_impl.cpp @@ -0,0 +1,107 @@ +class public_key_impl +{ + public: + public_key_impl() : _key(nullptr) + { + init_lib(); + } + + public_key_impl( const public_key_impl& cpy ) : _key(nullptr) + { + init_lib(); + *this = cpy; + } + + public_key_impl( public_key_impl&& cpy ) : _key(nullptr) + { + init_lib(); + *this = cpy; + } + + ~public_key_impl() + { + free_key(); + } + + public_key_impl& operator=( const public_key_impl& pk ) + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = dup_key( pk._key ); + } else { + copy_key( _key, pk._key ); + } + return *this; + } + + public_key_impl& operator=( public_key_impl&& pk ) + { + free_key(); + _key = pk._key; + pk._key = nullptr; + return *this; + } + + pub_data_type* _key; + + private: + void free_key(); + pub_data_type* dup_key( const pub_data_type* cpy ); + void copy_key( pub_data_type* to, const pub_data_type* from ); +}; + +class private_key_impl +{ + public: + private_key_impl() : _key(nullptr) + { + init_lib(); + } + + private_key_impl( const private_key_impl& cpy ) : _key(nullptr) + { + init_lib(); + *this = cpy; + } + + private_key_impl( private_key_impl&& cpy ) : _key(nullptr) + { + init_lib(); + *this = cpy; + } + + ~private_key_impl() + { + free_key(); + } + + private_key_impl& operator=( const private_key_impl& pk ) + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = dup_key( pk._key ); + } else { + copy_key( _key, pk._key ); + } + return *this; + } + + private_key_impl& operator=( private_key_impl&& pk ) + { + free_key(); + _key = pk._key; + pk._key = nullptr; + return *this; + } + + priv_data_type* _key; + + private: + void free_key(); + priv_data_type* dup_key( const priv_data_type* cpy ); + void copy_key( priv_data_type* to, const priv_data_type* from ); +}; diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index eef3d9e..6a67788 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -12,50 +12,59 @@ namespace fc { namespace ecc { namespace detail { - class public_key_impl - { - public: - public_key_impl() - :_key(nullptr) - { - static int init = init_openssl(); - } + static void init_lib() + { + static int init = init_openssl(); + } - ~public_key_impl() - { + typedef EC_KEY pub_data_type; + typedef EC_KEY priv_data_type; + + #include "_elliptic_impl.cpp" + + void public_key_impl::free_key() + { if( _key != nullptr ) { - EC_KEY_free(_key); + EC_KEY_free(_key); + _key = nullptr; } - } - public_key_impl( const public_key_impl& cpy ) - { - _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; - } - EC_KEY* _key; - }; - class private_key_impl - { - public: - private_key_impl() - :_key(nullptr) - { - static int init = init_openssl(); - } - ~private_key_impl() - { + } + + EC_KEY* public_key_impl::dup_key( const EC_KEY* cpy ) + { + return EC_KEY_dup( cpy ); + } + + void public_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) + { + // Group parameters etc. never change + EC_KEY_set_public_key( to, EC_KEY_get0_public_key( from ) ); + EC_KEY_set_private_key( to, EC_KEY_get0_private_key( from ) ); + } + + void private_key_impl::free_key() + { if( _key != nullptr ) { - EC_KEY_free(_key); + EC_KEY_free(_key); + _key = nullptr; } - } - private_key_impl( const private_key_impl& cpy ) - { - _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; - } - EC_KEY* _key; - }; + } + + EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) + { + return EC_KEY_dup( cpy ); + } + + void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) + { + // Group parameters etc. never change + EC_KEY_set_public_key( to, EC_KEY_get0_public_key( from ) ); + EC_KEY_set_private_key( to, EC_KEY_get0_private_key( from ) ); + } } + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) { if (*olen < SHA512_DIGEST_LENGTH) { @@ -204,10 +213,6 @@ namespace fc { namespace ecc { // // return rtn; // } - bool public_key::valid()const - { - return my->_key != nullptr; - } public_key public_key::add( const fc::sha256& digest )const { try { @@ -259,9 +264,6 @@ namespace fc { namespace ecc { return to_base58( key ); } - private_key::private_key() - {} - private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; @@ -336,14 +338,6 @@ namespace fc { namespace ecc { return dat; } - public_key::public_key() - { - } - - public_key::~public_key() - { - } - public_key::public_key( const public_key_point_data& dat ) { const char* front = &dat.data[0]; @@ -396,10 +390,6 @@ namespace fc { namespace ecc { return buf; } - private_key::~private_key() - { - } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) { int nV = c.data[0]; @@ -500,66 +490,6 @@ namespace fc { namespace ecc { } // while true } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); } - - private_key& private_key::operator=( private_key&& pk ) - { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = pk.my->_key; - pk.my->_key = nullptr; - return *this; - } - - public_key::public_key( const public_key& pk ) - :my(pk.my) - { - } - - public_key::public_key( public_key&& pk ) - :my( fc::move( pk.my) ) - { - } - - private_key::private_key( const private_key& pk ) - :my(pk.my) - { - } - - private_key::private_key( private_key&& pk ) - :my( fc::move( pk.my) ) - { - } - - public_key& public_key::operator=( public_key&& pk ) - { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = pk.my->_key; - pk.my->_key = nullptr; - return *this; - } - public_key& public_key::operator=( const public_key& pk ) - { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = EC_KEY_dup(pk.my->_key); - return *this; - } - private_key& private_key::operator=( const private_key& pk ) - { - if( my->_key ) - { - EC_KEY_free(my->_key); - } - my->_key = EC_KEY_dup(pk.my->_key); - return *this; - } } } diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 73ecf24..dca9d99 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -22,265 +22,54 @@ namespace fc { namespace ecc { } } - class public_key_impl - { - public: - public_key_impl() : _key(nullptr) - { - init_lib(); - } + typedef public_key_data pub_data_type; + typedef private_key_secret priv_data_type; - public_key_impl( const public_key_impl& cpy ) - { - init_lib(); - _key = nullptr; - *this = cpy; - } + #include "_elliptic_impl.cpp" - public_key_impl( public_key_impl&& cpy ) - { - init_lib(); - _key = nullptr; - *this = cpy; - } - - ~public_key_impl() - { + void public_key_impl::free_key() + { if( _key != nullptr ) { - delete _key; - _key = nullptr; + delete _key; + _key = nullptr; } - } + } - public_key_impl& operator=( const public_key_impl& pk ) - { - if (pk._key == nullptr) - { - if (_key != nullptr) - { - delete _key; - _key = nullptr; - } - } else if ( _key == nullptr ) { - _key = new public_key_data(*pk._key); - } else { - *_key = *pk._key; - } - return *this; - } + public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) + { + return new public_key_data( *cpy ); + } - public_key_impl& operator=( public_key_impl&& pk ) - { - if (_key != nullptr) - { - delete _key; - } - _key = pk._key; - pk._key = nullptr; - return *this; - } + void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) + { + *to = *from; + } - public_key_data *_key; - }; - class private_key_impl - { - public: - private_key_impl() : _key(nullptr) - { - init_lib(); - } - - private_key_impl( const private_key_impl& cpy ) - { - init_lib(); - _key = nullptr; - *this = cpy; - } - - private_key_impl( private_key_impl&& cpy ) - { - init_lib(); - _key = nullptr; - *this = cpy; - } - - ~private_key_impl() - { + void private_key_impl::free_key() + { if( _key != nullptr ) { - delete _key; - _key = nullptr; + delete _key; + _key = nullptr; } - } + } - private_key_impl& operator=( const private_key_impl& pk ) - { - if (pk._key == nullptr) - { - if (_key != nullptr) - { - delete _key; - _key = nullptr; - } - } else if ( _key == nullptr ) { - _key = new private_key_secret(*pk._key); - } else { - *_key = *pk._key; - } - return *this; - } + private_key_secret* private_key_impl::dup_key( const private_key_secret* cpy ) + { + return new private_key_secret( *cpy ); + } - private_key_impl& operator=( private_key_impl&& pk ) - { - if (_key != nullptr) - { - delete _key; - } - _key = pk._key; - pk._key = nullptr; - return *this; - } - - private_key_secret *_key; - }; + void private_key_impl::copy_key( private_key_secret* to, const private_key_secret* from ) + { + *to = *from; + } } -// static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) -// { -// if (*olen < SHA512_DIGEST_LENGTH) { -// return NULL; -// } -// *olen = SHA512_DIGEST_LENGTH; -// return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); -// } -// -// // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields -// // recid selects which key is recovered -// // if check is non-zero, additional checks are performed -// static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) -// { -// if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); -// -// int ret = 0; -// BN_CTX *ctx = NULL; -// -// BIGNUM *x = NULL; -// BIGNUM *e = NULL; -// BIGNUM *order = NULL; -// BIGNUM *sor = NULL; -// BIGNUM *eor = NULL; -// BIGNUM *field = NULL; -// EC_POINT *R = NULL; -// EC_POINT *O = NULL; -// EC_POINT *Q = NULL; -// BIGNUM *rr = NULL; -// BIGNUM *zero = NULL; -// int n = 0; -// int i = recid / 2; -// -// const EC_GROUP *group = EC_KEY_get0_group(eckey); -// if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } -// BN_CTX_start(ctx); -// order = BN_CTX_get(ctx); -// if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } -// x = BN_CTX_get(ctx); -// if (!BN_copy(x, order)) { ret=-1; goto err; } -// if (!BN_mul_word(x, i)) { ret=-1; goto err; } -// if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } -// field = BN_CTX_get(ctx); -// if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } -// if (BN_cmp(x, field) >= 0) { ret=0; goto err; } -// if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } -// if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } -// if (check) -// { -// if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } -// if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } -// if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } -// } -// if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } -// n = EC_GROUP_get_degree(group); -// e = BN_CTX_get(ctx); -// if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } -// if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); -// zero = BN_CTX_get(ctx); -// if (!BN_zero(zero)) { ret=-1; goto err; } -// if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } -// rr = BN_CTX_get(ctx); -// if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } -// sor = BN_CTX_get(ctx); -// if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } -// eor = BN_CTX_get(ctx); -// if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } -// if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } -// if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } -// -// ret = 1; -// -// err: -// if (ctx) { -// BN_CTX_end(ctx); -// BN_CTX_free(ctx); -// } -// if (R != NULL) EC_POINT_free(R); -// if (O != NULL) EC_POINT_free(O); -// if (Q != NULL) EC_POINT_free(Q); -// return ret; -// } -// -// -// int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) -// { -// int ok = 0; -// BN_CTX *ctx = NULL; -// EC_POINT *pub_key = NULL; -// -// if (!eckey) return 0; -// -// const EC_GROUP *group = EC_KEY_get0_group(eckey); -// -// if ((ctx = BN_CTX_new()) == NULL) -// goto err; -// -// pub_key = EC_POINT_new(group); -// -// if (pub_key == NULL) -// goto err; -// -// if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) -// goto err; -// -// EC_KEY_set_private_key(eckey,priv_key); -// EC_KEY_set_public_key(eckey,pub_key); -// -// ok = 1; -// -// err: -// -// if (pub_key) EC_POINT_free(pub_key); -// if (ctx != NULL) BN_CTX_free(ctx); -// -// return(ok); -// } public_key public_key::from_key_data( const public_key_data &data ) { return public_key(data); } -// public_key public_key::mult( const fc::sha256& digest )const -// { -// FC_ASSERT( my->_key != nullptr ); -// public_key_data new_key; -// memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); -// FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); -// return public_key( new_key ); -// } - - bool public_key::valid()const - { - return my->_key != nullptr; - } - public_key public_key::add( const fc::sha256& digest )const { FC_ASSERT( my->_key != nullptr ); @@ -296,9 +85,6 @@ namespace fc { namespace ecc { return to_base58( *my->_key ); } - private_key::private_key() - {} - private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; @@ -334,14 +120,6 @@ namespace fc { namespace ecc { return dat; } - public_key::public_key() - { - } - - public_key::~public_key() - { - } - public_key::public_key( const public_key_point_data& dat ) { const char* front = &dat.data[0]; @@ -382,10 +160,6 @@ namespace fc { namespace ecc { return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } - private_key::~private_key() - { - } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) { int nV = c.data[0]; @@ -415,44 +189,6 @@ namespace fc { namespace ecc { result.begin()[0] = 27 + 4 + recid; return result; } - - private_key& private_key::operator=( private_key&& pk ) - { - my = std::move(pk.my); - return *this; - } - public_key::public_key( const public_key& pk ) - :my(pk.my) - { - } - public_key::public_key( public_key&& pk ) - :my( std::move(pk.my) ) - { - } - private_key::private_key( const private_key& pk ) - :my(pk.my) - { - } - private_key::private_key( private_key&& pk ) - :my( std::move( pk.my) ) - { - } - - public_key& public_key::operator=( public_key&& pk ) - { - my = std::move(pk.my); - return *this; - } - public_key& public_key::operator=( const public_key& pk ) - { - my = pk.my; - return *this; - } - private_key& private_key::operator=( const private_key& pk ) - { - my = pk.my; - return *this; - } } } From 55f9cf5e16fbb58f20df66619b6eb24b450bbfec Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 21:13:06 +0100 Subject: [PATCH 23/54] Added mixed implementation, fixed interop test script --- src/crypto/elliptic_mixed.cpp | 195 ++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/crypto/elliptic_mixed.cpp diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp new file mode 100644 index 0000000..dca9d99 --- /dev/null +++ b/src/crypto/elliptic_mixed.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace fc { namespace ecc { + namespace detail + { + static void init_lib() { + static int init_s = 0; + static int init_o = init_openssl(); + if (!init_s) { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + init_s = 1; + } + } + + typedef public_key_data pub_data_type; + typedef private_key_secret priv_data_type; + + #include "_elliptic_impl.cpp" + + void public_key_impl::free_key() + { + if( _key != nullptr ) + { + delete _key; + _key = nullptr; + } + } + + public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) + { + return new public_key_data( *cpy ); + } + + void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) + { + *to = *from; + } + + void private_key_impl::free_key() + { + if( _key != nullptr ) + { + delete _key; + _key = nullptr; + } + } + + private_key_secret* private_key_impl::dup_key( const private_key_secret* cpy ) + { + return new private_key_secret( *cpy ); + } + + void private_key_impl::copy_key( private_key_secret* to, const private_key_secret* from ) + { + *to = *from; + } + } + + public_key public_key::from_key_data( const public_key_data &data ) { + return public_key(data); + } + + public_key public_key::add( const fc::sha256& digest )const + { + FC_ASSERT( my->_key != nullptr ); + public_key_data new_key; + memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + return public_key( new_key ); + } + + std::string public_key::to_base58() const + { + FC_ASSERT( my->_key != nullptr ); + return to_base58( *my->_key ); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = new private_key_secret(secret); + return self; + } + + fc::sha256 private_key::get_secret()const + { + FC_ASSERT( my->_key != nullptr ); + return *my->_key; + } + + private_key::private_key( EC_KEY* k ) + { + my->_key = new private_key_secret( get_secret( k ) ); + EC_KEY_free(k); + } + + public_key_data public_key::serialize()const + { + FC_ASSERT( my->_key != nullptr ); + return *my->_key; + } + public_key_point_data public_key::serialize_ecc_point()const + { + FC_ASSERT( my->_key != nullptr ); + public_key_point_data dat; + unsigned int pk_len = my->_key->size(); + memcpy( dat.begin(), my->_key->begin(), pk_len ); + FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); + FC_ASSERT( pk_len == dat.size() ); + return dat; + } + + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + EC_KEY *key = o2i_ECPublicKey( nullptr, (const unsigned char**)&front, sizeof(dat) ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + my->_key = new public_key_data(); + i2o_ECPublicKey( key, (unsigned char**)&my->_key->data ); + EC_KEY_free( key ); + } + } + + public_key::public_key( const public_key_data& dat ) + { + my->_key = new public_key_data(dat); + } + + public_key private_key::get_public_key()const + { + FC_ASSERT( my->_key != nullptr ); + public_key_data pub; + unsigned int pk_len; + FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key->data(), 1 ) ); + FC_ASSERT( pk_len == pub.size() ); + return public_key(pub); + } + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + public_key_data pub(*other.my->_key); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key->data() ) ); +// ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); + return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); + } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + my->_key = new public_key_data(); + unsigned int pk_len; + FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( pk_len == my->_key->size() ); + } + + compact_signature private_key::sign_compact( const fc::sha256& digest )const + { + FC_ASSERT( my->_key != nullptr ); + compact_signature result; + int recid; + do + { + FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), NULL, NULL, &recid )); + } while( !public_key::is_canonical( result ) ); + result.begin()[0] = 27 + 4 + recid; + return result; + } +} +} + +#include "_elliptic_common.cpp" From bab386443797701d10afd827d16f1d126dd1f186 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 21:15:55 +0100 Subject: [PATCH 24/54] Added mixed implementation, fixed interop test script --- CMakeLists.txt | 7 +- src/crypto/_elliptic_common.cpp | 4 + src/crypto/elliptic_mixed.cpp | 302 ++++++++++++++++++++++++------ src/crypto/elliptic_openssl.cpp | 18 +- src/crypto/elliptic_secp256k1.cpp | 10 +- tests/ecc-interop.sh | 31 ++- 6 files changed, 283 insertions(+), 89 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2329374..a601187 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ SET( DEFAULT_LIBRARY_INSTALL_DIR lib/ ) SET( DEFAULT_EXECUTABLE_INSTALL_DIR bin/ ) SET( CMAKE_DEBUG_POSTFIX _debug ) SET( BUILD_SHARED_LIBS NO ) -SET( ECC_IMPL openssl CACHE STRING "openssl or secp256k1" ) +SET( ECC_IMPL openssl CACHE STRING "openssl or secp256k1 or mixed" ) set(platformBitness 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -37,9 +37,10 @@ SET(BOOST_COMPONENTS) LIST(APPEND BOOST_COMPONENTS thread date_time system filesystem program_options signals serialization chrono unit_test_framework context locale iostreams) SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) -IF( ECC_IMPL STREQUAL secp256k1 ) +IF( ECC_IMPL STREQUAL openssl ) +ELSE( ECC_IMPL STREQUAL openssl ) SET( ECC_LIB secp256k1 ) -ENDIF( ECC_IMPL STREQUAL secp256k1 ) +ENDIF( ECC_IMPL STREQUAL openssl ) IF( WIN32 ) MESSAGE(STATUS "Configuring fc to build on Win32") diff --git a/src/crypto/_elliptic_common.cpp b/src/crypto/_elliptic_common.cpp index 12c8c63..964aa57 100644 --- a/src/crypto/_elliptic_common.cpp +++ b/src/crypto/_elliptic_common.cpp @@ -7,6 +7,10 @@ namespace fc { namespace ecc { public_key::public_key( public_key&& pk ) : my( std::move(pk.my) ) {} + public_key public_key::from_key_data( const public_key_data &data ) { + return public_key(data); + } + bool public_key::valid()const { return my->_key != nullptr; diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp index dca9d99..8c7e531 100644 --- a/src/crypto/elliptic_mixed.cpp +++ b/src/crypto/elliptic_mixed.cpp @@ -23,7 +23,7 @@ namespace fc { namespace ecc { } typedef public_key_data pub_data_type; - typedef private_key_secret priv_data_type; + typedef EC_KEY priv_data_type; #include "_elliptic_impl.cpp" @@ -50,26 +50,22 @@ namespace fc { namespace ecc { { if( _key != nullptr ) { - delete _key; + EC_KEY_free(_key); _key = nullptr; } } - private_key_secret* private_key_impl::dup_key( const private_key_secret* cpy ) + EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) { - return new private_key_secret( *cpy ); + return EC_KEY_dup( cpy ); } - void private_key_impl::copy_key( private_key_secret* to, const private_key_secret* from ) + void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) { - *to = *from; + EC_KEY_copy( to, from ); } } - public_key public_key::from_key_data( const public_key_data &data ) { - return public_key(data); - } - public_key public_key::add( const fc::sha256& digest )const { FC_ASSERT( my->_key != nullptr ); @@ -85,25 +81,6 @@ namespace fc { namespace ecc { return to_base58( *my->_key ); } - private_key private_key::regenerate( const fc::sha256& secret ) - { - private_key self; - self.my->_key = new private_key_secret(secret); - return self; - } - - fc::sha256 private_key::get_secret()const - { - FC_ASSERT( my->_key != nullptr ); - return *my->_key; - } - - private_key::private_key( EC_KEY* k ) - { - my->_key = new private_key_secret( get_secret( k ) ); - EC_KEY_free(k); - } - public_key_data public_key::serialize()const { FC_ASSERT( my->_key != nullptr ); @@ -126,11 +103,13 @@ namespace fc { namespace ecc { if( *front == 0 ){} else { - EC_KEY *key = o2i_ECPublicKey( nullptr, (const unsigned char**)&front, sizeof(dat) ); + EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); FC_ASSERT( key ); EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); my->_key = new public_key_data(); - i2o_ECPublicKey( key, (unsigned char**)&my->_key->data ); + unsigned char* buffer = (unsigned char*) my->_key->begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling EC_KEY_free( key ); } } @@ -140,26 +119,6 @@ namespace fc { namespace ecc { my->_key = new public_key_data(dat); } - public_key private_key::get_public_key()const - { - FC_ASSERT( my->_key != nullptr ); - public_key_data pub; - unsigned int pk_len; - FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key->data(), 1 ) ); - FC_ASSERT( pk_len == pub.size() ); - return public_key(pub); - } - - fc::sha512 private_key::get_shared_secret( const public_key& other )const - { - FC_ASSERT( my->_key != nullptr ); - FC_ASSERT( other.my->_key != nullptr ); - public_key_data pub(*other.my->_key); - FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key->data() ) ); -// ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); - return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); - } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) { int nV = c.data[0]; @@ -177,17 +136,244 @@ namespace fc { namespace ecc { FC_ASSERT( pk_len == my->_key->size() ); } + + + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + { + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); + } + + // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields + // recid selects which key is recovered + // if check is non-zero, additional checks are performed + static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) + { + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + + err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; + } + + int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) + { + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) + { + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); + } + return self; + } + + fc::sha256 private_key::get_secret()const + { + return get_secret( my->_key ); + } + + private_key::private_key( EC_KEY* k ) + { + my->_key = k; + } + + public_key private_key::get_public_key()const + { + public_key_data data; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + unsigned char* buffer = (unsigned char*) data.begin(); + i2o_ECPublicKey( my->_key, &buffer ); // FIXME: questionable memory handling + return public_key( data ); + } + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + fc::sha512 buf; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + const unsigned char* buffer = (const unsigned char*) other.my->_key->begin(); + o2i_ECPublicKey( &key, &buffer, sizeof(*other.my->_key) ); + ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(key), my->_key, ecies_key_derivation ); + EC_KEY_free(key); + return buf; + } + compact_signature private_key::sign_compact( const fc::sha256& digest )const { + try { FC_ASSERT( my->_key != nullptr ); - compact_signature result; - int recid; - do + auto my_pub_key = get_public_key().serialize(); // just for good measure + //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + public_key_data key_data; + while( true ) { - FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), NULL, NULL, &recid )); - } while( !public_key::is_canonical( result ) ); - result.begin()[0] = 27 + 4 + recid; - return result; + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + compact_signature csig; + // memset( csig.data, 0, sizeof(csig) ); + + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + for (int i=0; i<4; i++) + { + if (ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) + { + unsigned char* buffer = (unsigned char*) key_data.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + if ( key_data == my_pub_key ) + { + nRecId = i; + break; + } + } + } + EC_KEY_free( key ); + + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); + if( lenR != 32 ) { free(result); continue; } + if( lenS != 32 ) { free(result); continue; } + //idump( (33-(nBitsR+7)/8) ); + //idump( (65-(nBitsS+7)/8) ); + //idump( (sizeof(csig) ) ); + memcpy( &csig.data[1], &result[4], lenR ); + memcpy( &csig.data[33], &result[6+lenR], lenS ); + //idump( (csig.data[33]) ); + //idump( (csig.data[1]) ); + free(result); + //idump( (nRecId) ); + csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); + /* + idump( (csig) ); + auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); + auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); + idump( (rlen)(slen) ); + */ + } + return csig; + } // while true + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); } } } diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 6a67788..414123f 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -38,9 +38,7 @@ namespace fc { namespace ecc { void public_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) { - // Group parameters etc. never change - EC_KEY_set_public_key( to, EC_KEY_get0_public_key( from ) ); - EC_KEY_set_private_key( to, EC_KEY_get0_private_key( from ) ); + EC_KEY_copy( to, from ); } void private_key_impl::free_key() @@ -59,9 +57,7 @@ namespace fc { namespace ecc { void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) { - // Group parameters etc. never change - EC_KEY_set_public_key( to, EC_KEY_get0_public_key( from ) ); - EC_KEY_set_private_key( to, EC_KEY_get0_private_key( from ) ); + EC_KEY_copy( to, from ); } } @@ -183,10 +179,6 @@ namespace fc { namespace ecc { return(ok); } - public_key public_key::from_key_data( const public_key_data &data ) { - return public_key(data); - } - /* WARNING! This implementation is broken, it is actually equivalent to * public_key::add()! */ @@ -320,7 +312,7 @@ namespace fc { namespace ecc { /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ /*assert( nbytes == 33 )*/ char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling return dat; /* EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); @@ -334,7 +326,7 @@ namespace fc { namespace ecc { if( !my->_key ) return dat; EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling return dat; } @@ -344,7 +336,7 @@ namespace fc { namespace ecc { if( *front == 0 ){} else { - /*my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); */ + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); if( !my->_key ) { diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index dca9d99..26f8d3d 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -66,10 +66,6 @@ namespace fc { namespace ecc { } } - public_key public_key::from_key_data( const public_key_data &data ) { - return public_key(data); - } - public_key public_key::add( const fc::sha256& digest )const { FC_ASSERT( my->_key != nullptr ); @@ -126,11 +122,13 @@ namespace fc { namespace ecc { if( *front == 0 ){} else { - EC_KEY *key = o2i_ECPublicKey( nullptr, (const unsigned char**)&front, sizeof(dat) ); + EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); FC_ASSERT( key ); EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); my->_key = new public_key_data(); - i2o_ECPublicKey( key, (unsigned char**)&my->_key->data ); + unsigned char* buffer = (unsigned char*) my->_key->begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling EC_KEY_free( key ); } } diff --git a/tests/ecc-interop.sh b/tests/ecc-interop.sh index 50ff0c7..98d2b69 100755 --- a/tests/ecc-interop.sh +++ b/tests/ecc-interop.sh @@ -18,18 +18,31 @@ make ecc_test mv ecc_test ecc_test.secp256k1 ) >/dev/null 2>&1 +echo Building ecc_test with mixed... +( +cmake -D ECC_IMPL=mixed . +make ecc_test +mv ecc_test ecc_test.mixed +) >/dev/null 2>&1 + run () { - echo "Running ecc_test.$1 test ecc.interop.$1 ..." - $TIME "./ecc_test.$1" test "ecc.interop.$1" + echo "Running ecc_test.$1 test ecc.interop.$2 ..." + $TIME "./ecc_test.$1" test "ecc.interop.$2" } -run openssl -run openssl -run secp256k1 -run secp256k1 -run secp256k1 -run openssl +run openssl openssl +run openssl openssl +run secp256k1 secp256k1 +run secp256k1 secp256k1 +run mixed mixed +run mixed mixed +run openssl secp256k1 +run openssl mixed +run secp256k1 openssl +run secp256k1 mixed +run mixed openssl +run mixed secp256k1 echo Done. -rm -f ecc_test.openssl ecc_test.secp256k1 ecc.interop.openssl ecc.interop.secp256k1 +rm -f ecc_test.openssl ecc_test.secp256k1 ecc_test.mixed ecc.interop.openssl ecc.interop.secp256k1 ecc.interop.mixed From 2f383f078f9d96f6b24acd67d37deb717e1e4df3 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 21:56:20 +0100 Subject: [PATCH 25/54] More refactoring --- README-ecc.md | 28 +- src/crypto/_elliptic_mixed_openssl.cpp | 237 ++++++++++++++++ src/crypto/_elliptic_mixed_secp256k1.cpp | 101 +++++++ src/crypto/elliptic_mixed.cpp | 338 +---------------------- src/crypto/elliptic_openssl.cpp | 234 +--------------- src/crypto/elliptic_secp256k1.cpp | 104 +------ 6 files changed, 358 insertions(+), 684 deletions(-) create mode 100644 src/crypto/_elliptic_mixed_openssl.cpp create mode 100644 src/crypto/_elliptic_mixed_secp256k1.cpp diff --git a/README-ecc.md b/README-ecc.md index 8c1046a..3a557f7 100644 --- a/README-ecc.md +++ b/README-ecc.md @@ -7,14 +7,15 @@ wrapper classes handling elliptic curve cryptography. Two implementations of this interface exist. One is based on OpenSSL, the other is based on libsecp256k1 (see https://github.com/bitcoin/secp256k1 ). The implementation to be used is selected at compile time using the -cmake variable "ECC_IMPL". It can take two values, openssl or secp256k1 . -The default is "openssl". The alternative can be configured when invoking +cmake variable "ECC_IMPL". It can take one of three values, openssl or +secp256k1 or mixed . +The default is "openssl". The alternatives can be configured when invoking cmake, for example cmake -D ECC_IMPL=secp256k1 . -If secp256k1 is chosen, the secp256k1 library and its include file must -already be installed in the appropriate library / include directories on +If secp256k1 or mixed is chosen, the secp256k1 library and its include file +must already be installed in the appropriate library / include directories on your system. @@ -36,20 +37,7 @@ the current ECC backend are written to it. If the file does exist, intermediate results from the current ECC backend are compared with the file contents. -For a full round of interoperability testing, you need to do this: - -1. Build ecc_test with openssl backend. -2. Run "ecc_test test ecc.interop.openssl". -3. Run "ecc_test test ecc.interop.openssl" again, testing openssl against - itself. -4. Build ecc_test with secp256k1 backend. -5. Run "ecc_test test ecc.interop.secp256k1". -6. Run "ecc_test test ecc.interop.secp256k1" again, testing secp256k1 against - itself. -7. Run "ecc_test test ecc.interop.openssl", testing secp256k1 against openssl. -8. Build ecc_test with openssl backend. -9. Run "ecc_test test ecc.interop.secp256k1", testing openssl against secp256k1. - -None of the test runs should produce any output. The above steps are scripted -in tests//ecc-interop.sh . +For a full round of interoperability testing, you can use the script +tests/ecc-interop.sh . +None of the test runs should produce any output. diff --git a/src/crypto/_elliptic_mixed_openssl.cpp b/src/crypto/_elliptic_mixed_openssl.cpp new file mode 100644 index 0000000..d8602ab --- /dev/null +++ b/src/crypto/_elliptic_mixed_openssl.cpp @@ -0,0 +1,237 @@ +namespace detail +{ + void private_key_impl::free_key() + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + _key = nullptr; + } + } + + EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) + { + return EC_KEY_dup( cpy ); + } + + void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) + { + EC_KEY_copy( to, from ); + } +} + +static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) +{ + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); +} + +// Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields +// recid selects which key is recovered +// if check is non-zero, additional checks are performed +static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) +{ + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + +err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; +} + +int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) +{ + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); +} + +private_key private_key::regenerate( const fc::sha256& secret ) +{ + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) + { + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); + } + return self; +} + +fc::sha256 private_key::get_secret()const +{ + return get_secret( my->_key ); +} + +private_key::private_key( EC_KEY* k ) +{ + my->_key = k; +} + +compact_signature private_key::sign_compact( const fc::sha256& digest )const +{ + try { + FC_ASSERT( my->_key != nullptr ); + auto my_pub_key = get_public_key().serialize(); // just for good measure + //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + public_key_data key_data; + while( true ) + { + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + compact_signature csig; + // memset( csig.data, 0, sizeof(csig) ); + + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + for (int i=0; i<4; i++) + { + if (ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) + { + unsigned char* buffer = (unsigned char*) key_data.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + if ( key_data == my_pub_key ) + { + nRecId = i; + break; + } + } + } + EC_KEY_free( key ); + + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); + if( lenR != 32 ) { free(result); continue; } + if( lenS != 32 ) { free(result); continue; } + //idump( (33-(nBitsR+7)/8) ); + //idump( (65-(nBitsS+7)/8) ); + //idump( (sizeof(csig) ) ); + memcpy( &csig.data[1], &result[4], lenR ); + memcpy( &csig.data[33], &result[6+lenR], lenS ); + //idump( (csig.data[33]) ); + //idump( (csig.data[1]) ); + free(result); + //idump( (nRecId) ); + csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); + /* + idump( (csig) ); + auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); + auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); + idump( (rlen)(slen) ); + */ + } + return csig; + } // while true + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); +} diff --git a/src/crypto/_elliptic_mixed_secp256k1.cpp b/src/crypto/_elliptic_mixed_secp256k1.cpp new file mode 100644 index 0000000..b0e2792 --- /dev/null +++ b/src/crypto/_elliptic_mixed_secp256k1.cpp @@ -0,0 +1,101 @@ +namespace detail +{ + static void init_lib() { + static int init_s = 0; + static int init_o = init_openssl(); + if (!init_s) { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + init_s = 1; + } + } + + void public_key_impl::free_key() + { + if( _key != nullptr ) + { + delete _key; + _key = nullptr; + } + } + + public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) + { + return new public_key_data( *cpy ); + } + + void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) + { + *to = *from; + } +} + +public_key public_key::add( const fc::sha256& digest )const +{ + FC_ASSERT( my->_key != nullptr ); + public_key_data new_key; + memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + return public_key( new_key ); +} + +std::string public_key::to_base58() const +{ + FC_ASSERT( my->_key != nullptr ); + return to_base58( *my->_key ); +} + +public_key_data public_key::serialize()const +{ + FC_ASSERT( my->_key != nullptr ); + return *my->_key; +} + +public_key_point_data public_key::serialize_ecc_point()const +{ + FC_ASSERT( my->_key != nullptr ); + public_key_point_data dat; + unsigned int pk_len = my->_key->size(); + memcpy( dat.begin(), my->_key->begin(), pk_len ); + FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); + FC_ASSERT( pk_len == dat.size() ); + return dat; +} + +public_key::public_key( const public_key_point_data& dat ) +{ + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + my->_key = new public_key_data(); + unsigned char* buffer = (unsigned char*) my->_key->begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + EC_KEY_free( key ); + } +} + +public_key::public_key( const public_key_data& dat ) +{ + my->_key = new public_key_data(dat); +} + +public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) +{ + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + my->_key = new public_key_data(); + unsigned int pk_len; + FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( pk_len == my->_key->size() ); +} diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp index 8c7e531..8ff3f72 100644 --- a/src/crypto/elliptic_mixed.cpp +++ b/src/crypto/elliptic_mixed.cpp @@ -13,273 +13,15 @@ namespace fc { namespace ecc { namespace detail { - static void init_lib() { - static int init_s = 0; - static int init_o = init_openssl(); - if (!init_s) { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - init_s = 1; - } - } + static void init_lib(); { typedef public_key_data pub_data_type; typedef EC_KEY priv_data_type; #include "_elliptic_impl.cpp" - - void public_key_impl::free_key() - { - if( _key != nullptr ) - { - delete _key; - _key = nullptr; - } - } - - public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) - { - return new public_key_data( *cpy ); - } - - void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) - { - *to = *from; - } - - void private_key_impl::free_key() - { - if( _key != nullptr ) - { - EC_KEY_free(_key); - _key = nullptr; - } - } - - EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) - { - return EC_KEY_dup( cpy ); - } - - void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) - { - EC_KEY_copy( to, from ); - } } - public_key public_key::add( const fc::sha256& digest )const - { - FC_ASSERT( my->_key != nullptr ); - public_key_data new_key; - memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); - FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); - return public_key( new_key ); - } - - std::string public_key::to_base58() const - { - FC_ASSERT( my->_key != nullptr ); - return to_base58( *my->_key ); - } - - public_key_data public_key::serialize()const - { - FC_ASSERT( my->_key != nullptr ); - return *my->_key; - } - public_key_point_data public_key::serialize_ecc_point()const - { - FC_ASSERT( my->_key != nullptr ); - public_key_point_data dat; - unsigned int pk_len = my->_key->size(); - memcpy( dat.begin(), my->_key->begin(), pk_len ); - FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); - FC_ASSERT( pk_len == dat.size() ); - return dat; - } - - public_key::public_key( const public_key_point_data& dat ) - { - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); - FC_ASSERT( key ); - EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); - my->_key = new public_key_data(); - unsigned char* buffer = (unsigned char*) my->_key->begin(); - i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling - EC_KEY_free( key ); - } - } - - public_key::public_key( const public_key_data& dat ) - { - my->_key = new public_key_data(dat); - } - - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) - { - int nV = c.data[0]; - if (nV<27 || nV>=35) - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); - - if( check_canonical ) - { - FC_ASSERT( is_canonical( c ), "signature is not canonical" ); - } - - my->_key = new public_key_data(); - unsigned int pk_len; - FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); - FC_ASSERT( pk_len == my->_key->size() ); - } - - - - static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) - { - if (*olen < SHA512_DIGEST_LENGTH) { - return NULL; - } - *olen = SHA512_DIGEST_LENGTH; - return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); - } - - // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields - // recid selects which key is recovered - // if check is non-zero, additional checks are performed - static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) - { - if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); - - int ret = 0; - BN_CTX *ctx = NULL; - - BIGNUM *x = NULL; - BIGNUM *e = NULL; - BIGNUM *order = NULL; - BIGNUM *sor = NULL; - BIGNUM *eor = NULL; - BIGNUM *field = NULL; - EC_POINT *R = NULL; - EC_POINT *O = NULL; - EC_POINT *Q = NULL; - BIGNUM *rr = NULL; - BIGNUM *zero = NULL; - int n = 0; - int i = recid / 2; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } - BN_CTX_start(ctx); - order = BN_CTX_get(ctx); - if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } - x = BN_CTX_get(ctx); - if (!BN_copy(x, order)) { ret=-1; goto err; } - if (!BN_mul_word(x, i)) { ret=-1; goto err; } - if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } - field = BN_CTX_get(ctx); - if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } - if (BN_cmp(x, field) >= 0) { ret=0; goto err; } - if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } - if (check) - { - if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } - if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } - } - if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - n = EC_GROUP_get_degree(group); - e = BN_CTX_get(ctx); - if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } - if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); - zero = BN_CTX_get(ctx); - if (!BN_zero(zero)) { ret=-1; goto err; } - if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } - rr = BN_CTX_get(ctx); - if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } - sor = BN_CTX_get(ctx); - if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } - eor = BN_CTX_get(ctx); - if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } - if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } - if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } - - ret = 1; - - err: - if (ctx) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - if (R != NULL) EC_POINT_free(R); - if (O != NULL) EC_POINT_free(O); - if (Q != NULL) EC_POINT_free(Q); - return ret; - } - - int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) - { - int ok = 0; - BN_CTX *ctx = NULL; - EC_POINT *pub_key = NULL; - - if (!eckey) return 0; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - - if ((ctx = BN_CTX_new()) == NULL) - goto err; - - pub_key = EC_POINT_new(group); - - if (pub_key == NULL) - goto err; - - if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) - goto err; - - EC_KEY_set_private_key(eckey,priv_key); - EC_KEY_set_public_key(eckey,pub_key); - - ok = 1; - - err: - - if (pub_key) EC_POINT_free(pub_key); - if (ctx != NULL) BN_CTX_free(ctx); - - return(ok); - } - - private_key private_key::regenerate( const fc::sha256& secret ) - { - private_key self; - self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - - ssl_bignum bn; - BN_bin2bn( (const unsigned char*)&secret, 32, bn ); - - if( !EC_KEY_regenerate_key(self.my->_key,bn) ) - { - FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); - } - return self; - } - - fc::sha256 private_key::get_secret()const - { - return get_secret( my->_key ); - } - - private_key::private_key( EC_KEY* k ) - { - my->_key = k; - } + #include "_elliptic_mixed_openssl.cpp" public_key private_key::get_public_key()const { @@ -303,79 +45,7 @@ namespace fc { namespace ecc { return buf; } - compact_signature private_key::sign_compact( const fc::sha256& digest )const - { - try { - FC_ASSERT( my->_key != nullptr ); - auto my_pub_key = get_public_key().serialize(); // just for good measure - //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - public_key_data key_data; - while( true ) - { - ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - - if (sig==nullptr) - FC_THROW_EXCEPTION( exception, "Unable to sign" ); - - compact_signature csig; - // memset( csig.data, 0, sizeof(csig) ); - - int nBitsR = BN_num_bits(sig->r); - int nBitsS = BN_num_bits(sig->s); - if (nBitsR <= 256 && nBitsS <= 256) - { - int nRecId = -1; - EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - FC_ASSERT( key ); - EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); - for (int i=0; i<4; i++) - { - if (ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) - { - unsigned char* buffer = (unsigned char*) key_data.begin(); - i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling - if ( key_data == my_pub_key ) - { - nRecId = i; - break; - } - } - } - EC_KEY_free( key ); - - if (nRecId == -1) - { - FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); - } - unsigned char* result = nullptr; - auto bytes = i2d_ECDSA_SIG( sig, &result ); - auto lenR = result[3]; - auto lenS = result[5+lenR]; - //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); - if( lenR != 32 ) { free(result); continue; } - if( lenS != 32 ) { free(result); continue; } - //idump( (33-(nBitsR+7)/8) ); - //idump( (65-(nBitsS+7)/8) ); - //idump( (sizeof(csig) ) ); - memcpy( &csig.data[1], &result[4], lenR ); - memcpy( &csig.data[33], &result[6+lenR], lenS ); - //idump( (csig.data[33]) ); - //idump( (csig.data[1]) ); - free(result); - //idump( (nRecId) ); - csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); - /* - idump( (csig) ); - auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); - auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); - idump( (rlen)(slen) ); - */ - } - return csig; - } // while true - } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); - } -} -} + #include "_elliptic_mixed_secp256k1.cpp" +} } #include "_elliptic_common.cpp" diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 414123f..8208ca1 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -40,144 +40,9 @@ namespace fc { namespace ecc { { EC_KEY_copy( to, from ); } - - void private_key_impl::free_key() - { - if( _key != nullptr ) - { - EC_KEY_free(_key); - _key = nullptr; - } - } - - EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) - { - return EC_KEY_dup( cpy ); - } - - void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) - { - EC_KEY_copy( to, from ); - } } - static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) - { - if (*olen < SHA512_DIGEST_LENGTH) { - return NULL; - } - *olen = SHA512_DIGEST_LENGTH; - return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); - } - - // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields - // recid selects which key is recovered - // if check is non-zero, additional checks are performed - static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) - { - if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); - - int ret = 0; - BN_CTX *ctx = NULL; - - BIGNUM *x = NULL; - BIGNUM *e = NULL; - BIGNUM *order = NULL; - BIGNUM *sor = NULL; - BIGNUM *eor = NULL; - BIGNUM *field = NULL; - EC_POINT *R = NULL; - EC_POINT *O = NULL; - EC_POINT *Q = NULL; - BIGNUM *rr = NULL; - BIGNUM *zero = NULL; - int n = 0; - int i = recid / 2; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } - BN_CTX_start(ctx); - order = BN_CTX_get(ctx); - if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } - x = BN_CTX_get(ctx); - if (!BN_copy(x, order)) { ret=-1; goto err; } - if (!BN_mul_word(x, i)) { ret=-1; goto err; } - if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } - field = BN_CTX_get(ctx); - if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } - if (BN_cmp(x, field) >= 0) { ret=0; goto err; } - if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } - if (check) - { - if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } - if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } - } - if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - n = EC_GROUP_get_degree(group); - e = BN_CTX_get(ctx); - if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } - if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); - zero = BN_CTX_get(ctx); - if (!BN_zero(zero)) { ret=-1; goto err; } - if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } - rr = BN_CTX_get(ctx); - if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } - sor = BN_CTX_get(ctx); - if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } - eor = BN_CTX_get(ctx); - if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } - if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } - if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } - - ret = 1; - - err: - if (ctx) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - if (R != NULL) EC_POINT_free(R); - if (O != NULL) EC_POINT_free(O); - if (Q != NULL) EC_POINT_free(Q); - return ret; - } - - - int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) - { - int ok = 0; - BN_CTX *ctx = NULL; - EC_POINT *pub_key = NULL; - - if (!eckey) return 0; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - - if ((ctx = BN_CTX_new()) == NULL) - goto err; - - pub_key = EC_POINT_new(group); - - if (pub_key == NULL) - goto err; - - if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) - goto err; - - EC_KEY_set_private_key(eckey,priv_key); - EC_KEY_set_public_key(eckey,pub_key); - - ok = 1; - - err: - - if (pub_key) EC_POINT_free(pub_key); - if (ctx != NULL) BN_CTX_free(ctx); - - return(ok); - } + #include "_elliptic_mixed_openssl.cpp" /* WARNING! This implementation is broken, it is actually equivalent to * public_key::add()! @@ -256,32 +121,6 @@ namespace fc { namespace ecc { return to_base58( key ); } - private_key private_key::regenerate( const fc::sha256& secret ) - { - private_key self; - self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - - ssl_bignum bn; - BN_bin2bn( (const unsigned char*)&secret, 32, bn ); - - if( !EC_KEY_regenerate_key(self.my->_key,bn) ) - { - FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); - } - return self; - } - - fc::sha256 private_key::get_secret()const - { - return get_secret( my->_key ); - } - - private_key::private_key( EC_KEY* k ) - { - my->_key = k; - } - // signature private_key::sign( const fc::sha256& digest )const // { // unsigned int buf_len = ECDSA_size(my->_key); @@ -415,74 +254,7 @@ namespace fc { namespace ecc { FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); } - compact_signature private_key::sign_compact( const fc::sha256& digest )const - { - try { - FC_ASSERT( my->_key != nullptr ); - auto my_pub_key = get_public_key().serialize(); // just for good measure - //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - while( true ) - { - ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - - if (sig==nullptr) - FC_THROW_EXCEPTION( exception, "Unable to sign" ); - - compact_signature csig; - // memset( csig.data, 0, sizeof(csig) ); - - int nBitsR = BN_num_bits(sig->r); - int nBitsS = BN_num_bits(sig->s); - if (nBitsR <= 256 && nBitsS <= 256) - { - int nRecId = -1; - for (int i=0; i<4; i++) - { - public_key keyRec; - keyRec.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if (ECDSA_SIG_recover_key_GFp(keyRec.my->_key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) - { - if (keyRec.serialize() == my_pub_key ) - { - nRecId = i; - break; - } - } - } - - if (nRecId == -1) - { - FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); - } - unsigned char* result = nullptr; - auto bytes = i2d_ECDSA_SIG( sig, &result ); - auto lenR = result[3]; - auto lenS = result[5+lenR]; - //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); - if( lenR != 32 ) { free(result); continue; } - if( lenS != 32 ) { free(result); continue; } - //idump( (33-(nBitsR+7)/8) ); - //idump( (65-(nBitsS+7)/8) ); - //idump( (sizeof(csig) ) ); - memcpy( &csig.data[1], &result[4], lenR ); - memcpy( &csig.data[33], &result[6+lenR], lenS ); - //idump( (csig.data[33]) ); - //idump( (csig.data[1]) ); - free(result); - //idump( (nRecId) ); - csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); - /* - idump( (csig) ); - auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); - auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); - idump( (rlen)(slen) ); - */ - } - return csig; - } // while true - } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); - } -} -} + #include "_elliptic_mixed_openssl.cpp" +} } #include "_elliptic_common.cpp" diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 26f8d3d..e71213c 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -13,39 +13,13 @@ namespace fc { namespace ecc { namespace detail { - static void init_lib() { - static int init_s = 0; - static int init_o = init_openssl(); - if (!init_s) { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - init_s = 1; - } - } + static void init_lib(); typedef public_key_data pub_data_type; typedef private_key_secret priv_data_type; #include "_elliptic_impl.cpp" - void public_key_impl::free_key() - { - if( _key != nullptr ) - { - delete _key; - _key = nullptr; - } - } - - public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) - { - return new public_key_data( *cpy ); - } - - void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) - { - *to = *from; - } - void private_key_impl::free_key() { if( _key != nullptr ) @@ -66,21 +40,6 @@ namespace fc { namespace ecc { } } - public_key public_key::add( const fc::sha256& digest )const - { - FC_ASSERT( my->_key != nullptr ); - public_key_data new_key; - memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); - FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); - return public_key( new_key ); - } - - std::string public_key::to_base58() const - { - FC_ASSERT( my->_key != nullptr ); - return to_base58( *my->_key ); - } - private_key private_key::regenerate( const fc::sha256& secret ) { private_key self; @@ -100,44 +59,6 @@ namespace fc { namespace ecc { EC_KEY_free(k); } - public_key_data public_key::serialize()const - { - FC_ASSERT( my->_key != nullptr ); - return *my->_key; - } - public_key_point_data public_key::serialize_ecc_point()const - { - FC_ASSERT( my->_key != nullptr ); - public_key_point_data dat; - unsigned int pk_len = my->_key->size(); - memcpy( dat.begin(), my->_key->begin(), pk_len ); - FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); - FC_ASSERT( pk_len == dat.size() ); - return dat; - } - - public_key::public_key( const public_key_point_data& dat ) - { - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); - FC_ASSERT( key ); - EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); - my->_key = new public_key_data(); - unsigned char* buffer = (unsigned char*) my->_key->begin(); - i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling - EC_KEY_free( key ); - } - } - - public_key::public_key( const public_key_data& dat ) - { - my->_key = new public_key_data(dat); - } - public_key private_key::get_public_key()const { FC_ASSERT( my->_key != nullptr ); @@ -158,23 +79,6 @@ namespace fc { namespace ecc { return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) - { - int nV = c.data[0]; - if (nV<27 || nV>=35) - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); - - if( check_canonical ) - { - FC_ASSERT( is_canonical( c ), "signature is not canonical" ); - } - - my->_key = new public_key_data(); - unsigned int pk_len; - FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); - FC_ASSERT( pk_len == my->_key->size() ); - } - compact_signature private_key::sign_compact( const fc::sha256& digest )const { FC_ASSERT( my->_key != nullptr ); @@ -187,7 +91,9 @@ namespace fc { namespace ecc { result.begin()[0] = 27 + 4 + recid; return result; } -} -} + + #include "_elliptic_mixed_secp256k1.cpp" + +} } #include "_elliptic_common.cpp" From abab7617c8b2ada88a675515fb054a43aa565c5c Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Mar 2015 22:16:03 +0100 Subject: [PATCH 26/54] Minor fixes --- src/crypto/elliptic_mixed.cpp | 2 +- src/crypto/elliptic_openssl.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp index 8ff3f72..21621f2 100644 --- a/src/crypto/elliptic_mixed.cpp +++ b/src/crypto/elliptic_mixed.cpp @@ -13,7 +13,7 @@ namespace fc { namespace ecc { namespace detail { - static void init_lib(); { + static void init_lib(); typedef public_key_data pub_data_type; typedef EC_KEY priv_data_type; diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 8208ca1..d4025f4 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -253,8 +253,6 @@ namespace fc { namespace ecc { ECDSA_SIG_free(sig); FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); } - - #include "_elliptic_mixed_openssl.cpp" } } #include "_elliptic_common.cpp" From 66e64756000380d647da56316d0a0fd56fe7619b Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 11 Mar 2015 10:40:15 +0100 Subject: [PATCH 27/54] Fixed wording --- README-ecc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-ecc.md b/README-ecc.md index 3a557f7..b1df7a0 100644 --- a/README-ecc.md +++ b/README-ecc.md @@ -4,8 +4,8 @@ ECC Support include/fc/crypto/elliptic.hpp defines an interface for some cryptographic wrapper classes handling elliptic curve cryptography. -Two implementations of this interface exist. One is based on OpenSSL, the -other is based on libsecp256k1 (see https://github.com/bitcoin/secp256k1 ). +Three implementations of this interface exist. One is based on OpenSSL, the +others are based on libsecp256k1 (see https://github.com/bitcoin/secp256k1 ). The implementation to be used is selected at compile time using the cmake variable "ECC_IMPL". It can take one of three values, openssl or secp256k1 or mixed . From 1de9a3ba8786fb62a1121f226a227a950cdf3952 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 11 Mar 2015 11:07:14 +0100 Subject: [PATCH 28/54] Bugfix --- src/crypto/elliptic_secp256k1.cpp | 5 ++++- tests/ecc_test.cpp | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index e71213c..d270b6f 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -49,7 +49,10 @@ namespace fc { namespace ecc { fc::sha256 private_key::get_secret()const { - FC_ASSERT( my->_key != nullptr ); + if( !my->_key ) + { + return fc::sha256(); + } return *my->_key; } diff --git a/tests/ecc_test.cpp b/tests/ecc_test.cpp index 5492d61..df809ee 100644 --- a/tests/ecc_test.cpp +++ b/tests/ecc_test.cpp @@ -68,6 +68,8 @@ int main( int argc, char** argv ) interop_file(argv[2]); } + fc::ecc::private_key nullkey; + for( uint32_t i = 0; i < 3000; ++ i ) { try { @@ -76,6 +78,7 @@ int main( int argc, char** argv ) std::string pass(argv[1]); fc::sha256 h = fc::sha256::hash( pass.c_str(), pass.size() ); fc::ecc::private_key priv = fc::ecc::private_key::generate_from_seed(h); + FC_ASSERT( nullkey != priv ); interop_do(priv.get_secret()); fc::ecc::public_key pub = priv.get_public_key(); interop_do(pub.serialize()); From d69e67c032088b823c8175d35cc3cb50b2859dcd Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 11 Mar 2015 15:54:21 +0100 Subject: [PATCH 29/54] Work around too deterministic nonce --- src/crypto/elliptic_secp256k1.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index d270b6f..538c716 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -82,14 +82,23 @@ namespace fc { namespace ecc { return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } + static int extended_nonce_function( unsigned char *nonce32, const unsigned char *msg32, + const unsigned char *key32, unsigned int attempt, + const void *data ) { + unsigned int* extra = (unsigned int*) data; + (*extra)++; + return secp256k1_nonce_function_default( nonce32, msg32, key32, *extra, nullptr ); + } + compact_signature private_key::sign_compact( const fc::sha256& digest )const { FC_ASSERT( my->_key != nullptr ); compact_signature result; int recid; + unsigned int counter = 0; do { - FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), NULL, NULL, &recid )); + FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), extended_nonce_function, &counter, &recid )); } while( !public_key::is_canonical( result ) ); result.begin()[0] = 27 + 4 + recid; return result; From 10897adf1e81e68b5a17e5988b78e24157b41b83 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 11 Mar 2015 17:42:49 +0100 Subject: [PATCH 30/54] Hopefully this is more thread-safe --- src/crypto/_elliptic_mixed_secp256k1.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/crypto/_elliptic_mixed_secp256k1.cpp b/src/crypto/_elliptic_mixed_secp256k1.cpp index b0e2792..f5e7870 100644 --- a/src/crypto/_elliptic_mixed_secp256k1.cpp +++ b/src/crypto/_elliptic_mixed_secp256k1.cpp @@ -1,12 +1,13 @@ namespace detail { + static int init_secp256k1() { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + return 1; + } + static void init_lib() { - static int init_s = 0; + static int init_s = init_secp256k1(); static int init_o = init_openssl(); - if (!init_s) { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - init_s = 1; - } } void public_key_impl::free_key() From 05dee8669f2df7743b35645bf2b44cf426b201d0 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 13 Mar 2015 18:47:31 +0100 Subject: [PATCH 31/54] Much refactoring Fixed Move semantics in openssl impl Use in-place construction for secp256k1 Swapped implementations in mixed - sign with libsecp256k1, verify with openssl --- CMakeLists.txt | 8 + include/fc/crypto/elliptic.hpp | 4 +- src/crypto/_elliptic_impl.cpp | 107 ----- src/crypto/_elliptic_impl_priv.hpp | 23 + src/crypto/_elliptic_impl_pub.hpp | 32 ++ src/crypto/_elliptic_mixed_openssl.cpp | 237 ---------- src/crypto/_elliptic_mixed_secp256k1.cpp | 102 ----- ...lliptic_common.cpp => elliptic_common.cpp} | 49 +-- src/crypto/elliptic_impl_priv.cpp | 102 +++++ src/crypto/elliptic_impl_pub.cpp | 357 +++++++++++++++ src/crypto/elliptic_mixed.cpp | 43 +- src/crypto/elliptic_openssl.cpp | 413 +++++++++--------- src/crypto/elliptic_secp256k1.cpp | 186 ++++---- 13 files changed, 863 insertions(+), 800 deletions(-) delete mode 100644 src/crypto/_elliptic_impl.cpp create mode 100644 src/crypto/_elliptic_impl_priv.hpp create mode 100644 src/crypto/_elliptic_impl_pub.hpp delete mode 100644 src/crypto/_elliptic_mixed_openssl.cpp delete mode 100644 src/crypto/_elliptic_mixed_secp256k1.cpp rename src/crypto/{_elliptic_common.cpp => elliptic_common.cpp} (77%) create mode 100644 src/crypto/elliptic_impl_priv.cpp create mode 100644 src/crypto/elliptic_impl_pub.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a601187..0ffc8e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,8 +38,14 @@ LIST(APPEND BOOST_COMPONENTS thread date_time system filesystem program_options SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) IF( ECC_IMPL STREQUAL openssl ) +SET( ECC_REST src/crypto/elliptic_impl_pub.cpp ) ELSE( ECC_IMPL STREQUAL openssl ) SET( ECC_LIB secp256k1 ) +IF( ECC_IMPL STREQUAL mixed ) +SET( ECC_REST src/crypto/elliptic_impl_priv.cpp src/crypto/elliptic_impl_pub.cpp ) +ELSE( ECC_IMPL STREQUAL mixed ) +SET( ECC_REST src/crypto/elliptic_impl_priv.cpp ) +ENDIF( ECC_IMPL STREQUAL mixed ) ENDIF( ECC_IMPL STREQUAL openssl ) IF( WIN32 ) @@ -156,6 +162,8 @@ set( fc_sources src/crypto/sha512.cpp src/crypto/dh.cpp src/crypto/blowfish.cpp + src/crypto/elliptic_common.cpp + ${ECC_REST} src/crypto/elliptic_${ECC_IMPL}.cpp src/crypto/rand.cpp src/crypto/salsa20.cpp diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp index 7de417f..8c76788 100644 --- a/include/fc/crypto/elliptic.hpp +++ b/include/fc/crypto/elliptic.hpp @@ -72,7 +72,7 @@ namespace fc { friend class private_key; static public_key from_key_data( const public_key_data& v ); static bool is_canonical( const compact_signature& c ); - fc::fwd my; + fc::fwd my; }; /** @@ -132,7 +132,7 @@ namespace fc { private: private_key( EC_KEY* k ); static fc::sha256 get_secret( const EC_KEY * const k ); - fc::fwd my; + fc::fwd my; }; } // namespace ecc void to_variant( const ecc::private_key& var, variant& vo ); diff --git a/src/crypto/_elliptic_impl.cpp b/src/crypto/_elliptic_impl.cpp deleted file mode 100644 index 916e0a1..0000000 --- a/src/crypto/_elliptic_impl.cpp +++ /dev/null @@ -1,107 +0,0 @@ -class public_key_impl -{ - public: - public_key_impl() : _key(nullptr) - { - init_lib(); - } - - public_key_impl( const public_key_impl& cpy ) : _key(nullptr) - { - init_lib(); - *this = cpy; - } - - public_key_impl( public_key_impl&& cpy ) : _key(nullptr) - { - init_lib(); - *this = cpy; - } - - ~public_key_impl() - { - free_key(); - } - - public_key_impl& operator=( const public_key_impl& pk ) - { - if (pk._key == nullptr) - { - free_key(); - } else if ( _key == nullptr ) { - _key = dup_key( pk._key ); - } else { - copy_key( _key, pk._key ); - } - return *this; - } - - public_key_impl& operator=( public_key_impl&& pk ) - { - free_key(); - _key = pk._key; - pk._key = nullptr; - return *this; - } - - pub_data_type* _key; - - private: - void free_key(); - pub_data_type* dup_key( const pub_data_type* cpy ); - void copy_key( pub_data_type* to, const pub_data_type* from ); -}; - -class private_key_impl -{ - public: - private_key_impl() : _key(nullptr) - { - init_lib(); - } - - private_key_impl( const private_key_impl& cpy ) : _key(nullptr) - { - init_lib(); - *this = cpy; - } - - private_key_impl( private_key_impl&& cpy ) : _key(nullptr) - { - init_lib(); - *this = cpy; - } - - ~private_key_impl() - { - free_key(); - } - - private_key_impl& operator=( const private_key_impl& pk ) - { - if (pk._key == nullptr) - { - free_key(); - } else if ( _key == nullptr ) { - _key = dup_key( pk._key ); - } else { - copy_key( _key, pk._key ); - } - return *this; - } - - private_key_impl& operator=( private_key_impl&& pk ) - { - free_key(); - _key = pk._key; - pk._key = nullptr; - return *this; - } - - priv_data_type* _key; - - private: - void free_key(); - priv_data_type* dup_key( const priv_data_type* cpy ); - void copy_key( priv_data_type* to, const priv_data_type* from ); -}; diff --git a/src/crypto/_elliptic_impl_priv.hpp b/src/crypto/_elliptic_impl_priv.hpp new file mode 100644 index 0000000..2effaff --- /dev/null +++ b/src/crypto/_elliptic_impl_priv.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +/* private_key_impl based on libsecp256k1 + * used by mixed + secp256k1 + */ + +namespace fc { namespace ecc { namespace detail { + +void _init_lib(); + +class private_key_impl +{ + public: + private_key_impl() noexcept; + private_key_impl( const private_key_impl& cpy ) noexcept; + + private_key_impl& operator=( const private_key_impl& pk ) noexcept; + + private_key_secret _key; +}; + +}}} diff --git a/src/crypto/_elliptic_impl_pub.hpp b/src/crypto/_elliptic_impl_pub.hpp new file mode 100644 index 0000000..aa547be --- /dev/null +++ b/src/crypto/_elliptic_impl_pub.hpp @@ -0,0 +1,32 @@ +#pragma once +#include + +/* public_key_impl implementation based on openssl + * used by mixed + openssl + */ + +namespace fc { namespace ecc { namespace detail { + +void _init_lib(); + +class public_key_impl +{ + public: + public_key_impl() noexcept; + public_key_impl( const public_key_impl& cpy ) noexcept; + public_key_impl( public_key_impl&& cpy ) noexcept; + ~public_key_impl() noexcept; + + public_key_impl& operator=( const public_key_impl& pk ) noexcept; + + public_key_impl& operator=( public_key_impl&& pk ) noexcept; + + static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check); + + EC_KEY* _key = nullptr; + + private: + void free_key() noexcept; +}; + +}}} diff --git a/src/crypto/_elliptic_mixed_openssl.cpp b/src/crypto/_elliptic_mixed_openssl.cpp deleted file mode 100644 index d8602ab..0000000 --- a/src/crypto/_elliptic_mixed_openssl.cpp +++ /dev/null @@ -1,237 +0,0 @@ -namespace detail -{ - void private_key_impl::free_key() - { - if( _key != nullptr ) - { - EC_KEY_free(_key); - _key = nullptr; - } - } - - EC_KEY* private_key_impl::dup_key( const EC_KEY* cpy ) - { - return EC_KEY_dup( cpy ); - } - - void private_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) - { - EC_KEY_copy( to, from ); - } -} - -static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) -{ - if (*olen < SHA512_DIGEST_LENGTH) { - return NULL; - } - *olen = SHA512_DIGEST_LENGTH; - return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); -} - -// Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields -// recid selects which key is recovered -// if check is non-zero, additional checks are performed -static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) -{ - if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); - - int ret = 0; - BN_CTX *ctx = NULL; - - BIGNUM *x = NULL; - BIGNUM *e = NULL; - BIGNUM *order = NULL; - BIGNUM *sor = NULL; - BIGNUM *eor = NULL; - BIGNUM *field = NULL; - EC_POINT *R = NULL; - EC_POINT *O = NULL; - EC_POINT *Q = NULL; - BIGNUM *rr = NULL; - BIGNUM *zero = NULL; - int n = 0; - int i = recid / 2; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } - BN_CTX_start(ctx); - order = BN_CTX_get(ctx); - if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } - x = BN_CTX_get(ctx); - if (!BN_copy(x, order)) { ret=-1; goto err; } - if (!BN_mul_word(x, i)) { ret=-1; goto err; } - if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } - field = BN_CTX_get(ctx); - if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } - if (BN_cmp(x, field) >= 0) { ret=0; goto err; } - if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } - if (check) - { - if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } - if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } - } - if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } - n = EC_GROUP_get_degree(group); - e = BN_CTX_get(ctx); - if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } - if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); - zero = BN_CTX_get(ctx); - if (!BN_zero(zero)) { ret=-1; goto err; } - if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } - rr = BN_CTX_get(ctx); - if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } - sor = BN_CTX_get(ctx); - if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } - eor = BN_CTX_get(ctx); - if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } - if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } - if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } - - ret = 1; - -err: - if (ctx) { - BN_CTX_end(ctx); - BN_CTX_free(ctx); - } - if (R != NULL) EC_POINT_free(R); - if (O != NULL) EC_POINT_free(O); - if (Q != NULL) EC_POINT_free(Q); - return ret; -} - -int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) -{ - int ok = 0; - BN_CTX *ctx = NULL; - EC_POINT *pub_key = NULL; - - if (!eckey) return 0; - - const EC_GROUP *group = EC_KEY_get0_group(eckey); - - if ((ctx = BN_CTX_new()) == NULL) - goto err; - - pub_key = EC_POINT_new(group); - - if (pub_key == NULL) - goto err; - - if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) - goto err; - - EC_KEY_set_private_key(eckey,priv_key); - EC_KEY_set_public_key(eckey,pub_key); - - ok = 1; - - err: - - if (pub_key) EC_POINT_free(pub_key); - if (ctx != NULL) BN_CTX_free(ctx); - - return(ok); -} - -private_key private_key::regenerate( const fc::sha256& secret ) -{ - private_key self; - self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); - - ssl_bignum bn; - BN_bin2bn( (const unsigned char*)&secret, 32, bn ); - - if( !EC_KEY_regenerate_key(self.my->_key,bn) ) - { - FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); - } - return self; -} - -fc::sha256 private_key::get_secret()const -{ - return get_secret( my->_key ); -} - -private_key::private_key( EC_KEY* k ) -{ - my->_key = k; -} - -compact_signature private_key::sign_compact( const fc::sha256& digest )const -{ - try { - FC_ASSERT( my->_key != nullptr ); - auto my_pub_key = get_public_key().serialize(); // just for good measure - //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - public_key_data key_data; - while( true ) - { - ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - - if (sig==nullptr) - FC_THROW_EXCEPTION( exception, "Unable to sign" ); - - compact_signature csig; - // memset( csig.data, 0, sizeof(csig) ); - - int nBitsR = BN_num_bits(sig->r); - int nBitsS = BN_num_bits(sig->s); - if (nBitsR <= 256 && nBitsS <= 256) - { - int nRecId = -1; - EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - FC_ASSERT( key ); - EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); - for (int i=0; i<4; i++) - { - if (ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) - { - unsigned char* buffer = (unsigned char*) key_data.begin(); - i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling - if ( key_data == my_pub_key ) - { - nRecId = i; - break; - } - } - } - EC_KEY_free( key ); - - if (nRecId == -1) - { - FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); - } - unsigned char* result = nullptr; - auto bytes = i2d_ECDSA_SIG( sig, &result ); - auto lenR = result[3]; - auto lenS = result[5+lenR]; - //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); - if( lenR != 32 ) { free(result); continue; } - if( lenS != 32 ) { free(result); continue; } - //idump( (33-(nBitsR+7)/8) ); - //idump( (65-(nBitsS+7)/8) ); - //idump( (sizeof(csig) ) ); - memcpy( &csig.data[1], &result[4], lenR ); - memcpy( &csig.data[33], &result[6+lenR], lenS ); - //idump( (csig.data[33]) ); - //idump( (csig.data[1]) ); - free(result); - //idump( (nRecId) ); - csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); - /* - idump( (csig) ); - auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); - auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); - idump( (rlen)(slen) ); - */ - } - return csig; - } // while true - } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); -} diff --git a/src/crypto/_elliptic_mixed_secp256k1.cpp b/src/crypto/_elliptic_mixed_secp256k1.cpp deleted file mode 100644 index f5e7870..0000000 --- a/src/crypto/_elliptic_mixed_secp256k1.cpp +++ /dev/null @@ -1,102 +0,0 @@ -namespace detail -{ - static int init_secp256k1() { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - return 1; - } - - static void init_lib() { - static int init_s = init_secp256k1(); - static int init_o = init_openssl(); - } - - void public_key_impl::free_key() - { - if( _key != nullptr ) - { - delete _key; - _key = nullptr; - } - } - - public_key_data* public_key_impl::dup_key( const public_key_data* cpy ) - { - return new public_key_data( *cpy ); - } - - void public_key_impl::copy_key( public_key_data* to, const public_key_data* from ) - { - *to = *from; - } -} - -public_key public_key::add( const fc::sha256& digest )const -{ - FC_ASSERT( my->_key != nullptr ); - public_key_data new_key; - memcpy( new_key.begin(), my->_key->begin(), new_key.size() ); - FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); - return public_key( new_key ); -} - -std::string public_key::to_base58() const -{ - FC_ASSERT( my->_key != nullptr ); - return to_base58( *my->_key ); -} - -public_key_data public_key::serialize()const -{ - FC_ASSERT( my->_key != nullptr ); - return *my->_key; -} - -public_key_point_data public_key::serialize_ecc_point()const -{ - FC_ASSERT( my->_key != nullptr ); - public_key_point_data dat; - unsigned int pk_len = my->_key->size(); - memcpy( dat.begin(), my->_key->begin(), pk_len ); - FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); - FC_ASSERT( pk_len == dat.size() ); - return dat; -} - -public_key::public_key( const public_key_point_data& dat ) -{ - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); - FC_ASSERT( key ); - EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); - my->_key = new public_key_data(); - unsigned char* buffer = (unsigned char*) my->_key->begin(); - i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling - EC_KEY_free( key ); - } -} - -public_key::public_key( const public_key_data& dat ) -{ - my->_key = new public_key_data(dat); -} - -public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) -{ - int nV = c.data[0]; - if (nV<27 || nV>=35) - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); - - if( check_canonical ) - { - FC_ASSERT( is_canonical( c ), "signature is not canonical" ); - } - - my->_key = new public_key_data(); - unsigned int pk_len; - FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key->begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); - FC_ASSERT( pk_len == my->_key->size() ); -} diff --git a/src/crypto/_elliptic_common.cpp b/src/crypto/elliptic_common.cpp similarity index 77% rename from src/crypto/_elliptic_common.cpp rename to src/crypto/elliptic_common.cpp index 964aa57..741682e 100644 --- a/src/crypto/_elliptic_common.cpp +++ b/src/crypto/elliptic_common.cpp @@ -1,21 +1,14 @@ +#include +#include + +/* stuff common to all ecc implementations */ + namespace fc { namespace ecc { - public_key::public_key() {} - - public_key::~public_key() {} - - public_key::public_key( const public_key& pk ) : my( pk.my ) {} - - public_key::public_key( public_key&& pk ) : my( std::move(pk.my) ) {} public_key public_key::from_key_data( const public_key_data &data ) { return public_key(data); } - bool public_key::valid()const - { - return my->_key != nullptr; - } - std::string public_key::to_base58( const public_key_data &key ) { uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; @@ -46,14 +39,6 @@ namespace fc { namespace ecc { && !(c.data[33] == 0 && !(c.data[34] & 0x80)); } - private_key::private_key() {} - - private_key::~private_key() {} - - private_key::private_key( const private_key& pk ) : my(pk.my) {} - - private_key::private_key( private_key&& pk ) : my( std::move( pk.my) ) {} - private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) { ssl_bignum z; @@ -108,29 +93,6 @@ namespace fc { namespace ecc { return private_key( k ); } - private_key& private_key::operator=( private_key&& pk ) - { - my = std::move(pk.my); - return *this; - } - - public_key& public_key::operator=( public_key&& pk ) - { - my = std::move(pk.my); - return *this; - } - - public_key& public_key::operator=( const public_key& pk ) - { - my = pk.my; - return *this; - } - - private_key& private_key::operator=( const private_key& pk ) - { - my = pk.my; - return *this; - } } void to_variant( const ecc::private_key& var, variant& vo ) @@ -156,4 +118,5 @@ void from_variant( const variant& var, ecc::public_key& vo ) from_variant( var, dat ); vo = ecc::public_key(dat); } + } diff --git a/src/crypto/elliptic_impl_priv.cpp b/src/crypto/elliptic_impl_priv.cpp new file mode 100644 index 0000000..60c458d --- /dev/null +++ b/src/crypto/elliptic_impl_priv.cpp @@ -0,0 +1,102 @@ +#include + +#include + +#include "_elliptic_impl_priv.hpp" + +/* used by mixed + secp256k1 */ + +namespace fc { namespace ecc { + namespace detail { + + private_key_impl::private_key_impl() noexcept + { + _init_lib(); + } + + private_key_impl::private_key_impl( const private_key_impl& cpy ) noexcept + { + _init_lib(); + this->_key = cpy._key; + } + + private_key_impl& private_key_impl::operator=( const private_key_impl& pk ) noexcept + { + _key = pk._key; + return *this; + } + } + + static const private_key_secret empty_priv; + + private_key::private_key() {} + + private_key::private_key( const private_key& pk ) : my( pk.my ) {} + + private_key::private_key( private_key&& pk ) : my( pk.my ) {} + + private_key::~private_key() {} + + private_key& private_key::operator=( private_key&& pk ) + { + my = pk.my; + return *this; + } + + private_key& private_key::operator=( const private_key& pk ) + { + my = pk.my; + return *this; + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = secret; + return self; + } + + fc::sha256 private_key::get_secret()const + { + return my->_key; + } + + private_key::private_key( EC_KEY* k ) + { + my->_key = get_secret( k ); + EC_KEY_free(k); + } + + public_key private_key::get_public_key()const + { + FC_ASSERT( my->_key != empty_priv ); + public_key_data pub; + unsigned int pk_len; + FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key.data(), 1 ) ); + FC_ASSERT( pk_len == pub.size() ); + return public_key(pub); + } + + static int extended_nonce_function( unsigned char *nonce32, const unsigned char *msg32, + const unsigned char *key32, unsigned int attempt, + const void *data ) { + unsigned int* extra = (unsigned int*) data; + (*extra)++; + return secp256k1_nonce_function_default( nonce32, msg32, key32, *extra, nullptr ); + } + + compact_signature private_key::sign_compact( const fc::sha256& digest )const + { + FC_ASSERT( my->_key != empty_priv ); + compact_signature result; + int recid; + unsigned int counter = 0; + do + { + FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key.data(), extended_nonce_function, &counter, &recid )); + } while( !public_key::is_canonical( result ) ); + result.begin()[0] = 27 + 4 + recid; + return result; + } + +}} diff --git a/src/crypto/elliptic_impl_pub.cpp b/src/crypto/elliptic_impl_pub.cpp new file mode 100644 index 0000000..d3f0724 --- /dev/null +++ b/src/crypto/elliptic_impl_pub.cpp @@ -0,0 +1,357 @@ +#include + +#include "_elliptic_impl_pub.hpp" + +/* used by mixed + openssl */ + +namespace fc { namespace ecc { + namespace detail { + + public_key_impl::public_key_impl() noexcept + { + _init_lib(); + } + + public_key_impl::public_key_impl( const public_key_impl& cpy ) noexcept + { + _init_lib(); + *this = cpy; + } + + public_key_impl::public_key_impl( public_key_impl&& cpy ) noexcept + { + _init_lib(); + *this = cpy; + } + + public_key_impl::~public_key_impl() noexcept + { + free_key(); + } + + public_key_impl& public_key_impl::operator=( const public_key_impl& pk ) noexcept + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = EC_KEY_dup( pk._key ); + } else { + EC_KEY_copy( _key, pk._key ); + } + return *this; + } + + public_key_impl& public_key_impl::operator=( public_key_impl&& pk ) noexcept + { + if ( this != &pk ) { + free_key(); + _key = pk._key; + pk._key = nullptr; + } + return *this; + } + + void public_key_impl::free_key() noexcept + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + _key = nullptr; + } + } + + // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields + // recid selects which key is recovered + // if check is non-zero, additional checks are performed + int public_key_impl::ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, + const unsigned char *msg, + int msglen, int recid, int check) + { + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + + err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; + } + } + + public_key::public_key() {} + + public_key::public_key( const public_key& pk ) : my( pk.my ) {} + + public_key::public_key( public_key&& pk ) : my( std::move( pk.my ) ) {} + + public_key::~public_key() {} + + public_key& public_key::operator=( public_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + public_key& public_key::operator=( const public_key& pk ) + { + my = pk.my; + return *this; + } + + bool public_key::valid()const + { + return my->_key != nullptr; + } + + /* WARNING! This implementation is broken, it is actually equivalent to + * public_key::add()! + */ +// public_key public_key::mult( const fc::sha256& digest ) const +// { +// // get point from this public key +// const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); +// ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); +// +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); +// +// // multiply by digest +// ssl_bignum one; +// BN_one(one); +// bn_ctx ctx(BN_CTX_new()); +// +// ec_point result(EC_POINT_new(group)); +// EC_POINT_mul(group, result, z, master_pub, one, ctx); +// +// public_key rtn; +// rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); +// EC_KEY_set_public_key(rtn.my->_key,result); +// +// return rtn; +// } + public_key public_key::add( const fc::sha256& digest )const + { + try { + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + + fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); + + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + if( digest_bi > fc::bigint(order) ) + { + FC_THROW_EXCEPTION( exception, "digest > group order" ); + } + + + public_key digest_key = private_key::regenerate(digest).get_public_key(); + const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); + + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest +// ssl_bignum one; +// BN_one(one); + + ec_point result(EC_POINT_new(group)); + EC_POINT_add(group, result, digest_point, master_pub, ctx); + + if (EC_POINT_is_at_infinity(group, result)) + { + FC_THROW_EXCEPTION( exception, "point at infinity" ); + } + + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + return rtn; + } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + } + + std::string public_key::to_base58() const + { + public_key_data key = serialize(); + return to_base58( key ); + } + +// signature private_key::sign( const fc::sha256& digest )const +// { +// unsigned int buf_len = ECDSA_size(my->_key); +//// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); +// signature sig; +// assert( buf_len == sizeof(sig) ); +// +// if( !ECDSA_sign( 0, +// (const unsigned char*)&digest, sizeof(digest), +// (unsigned char*)&sig, &buf_len, my->_key ) ) +// { +// FC_THROW_EXCEPTION( exception, "signing error" ); +// } +// +// +// return sig; +// } +// bool public_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key_data public_key::serialize()const + { + public_key_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ + /*assert( nbytes == 33 )*/ + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling + return dat; + /* + EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); + EC_GROUP* group = EC_KEY_get0_group( my->_key ); + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); + */ + } + public_key_point_data public_key::serialize_ecc_point()const + { + public_key_point_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling + return dat; + } + + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + public_key::public_key( const public_key_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + +// bool private_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + ECDSA_SIG *sig = ECDSA_SIG_new(); + BN_bin2bn(&c.data[1],32,sig->r); + BN_bin2bn(&c.data[33],32,sig->s); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); + + if (nV >= 31) + { + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + nV -= 4; +// fprintf( stderr, "compressed\n" ); + } + + if (detail::public_key_impl::ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) + { + ECDSA_SIG_free(sig); + return; + } + ECDSA_SIG_free(sig); + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + } +}} diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp index 21621f2..ac6f62d 100644 --- a/src/crypto/elliptic_mixed.cpp +++ b/src/crypto/elliptic_mixed.cpp @@ -10,42 +10,31 @@ #include #include +#include "_elliptic_impl_priv.hpp" +#include "_elliptic_impl_pub.hpp" + namespace fc { namespace ecc { namespace detail { - static void init_lib(); + static int init_secp256k1() { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + return 1; + } - typedef public_key_data pub_data_type; - typedef EC_KEY priv_data_type; - - #include "_elliptic_impl.cpp" - } - - #include "_elliptic_mixed_openssl.cpp" - - public_key private_key::get_public_key()const - { - public_key_data data; - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); - unsigned char* buffer = (unsigned char*) data.begin(); - i2o_ECPublicKey( my->_key, &buffer ); // FIXME: questionable memory handling - return public_key( data ); + void _init_lib() { + static int init_s = init_secp256k1(); + static int init_o = init_openssl(); + } } + static const private_key_secret empty_priv; fc::sha512 private_key::get_shared_secret( const public_key& other )const { - FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( my->_key != empty_priv ); FC_ASSERT( other.my->_key != nullptr ); - fc::sha512 buf; - EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - const unsigned char* buffer = (const unsigned char*) other.my->_key->begin(); - o2i_ECPublicKey( &key, &buffer, sizeof(*other.my->_key) ); - ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(key), my->_key, ecies_key_derivation ); - EC_KEY_free(key); - return buf; + public_key_data pub(other.serialize()); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); + return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } - #include "_elliptic_mixed_secp256k1.cpp" } } - -#include "_elliptic_common.cpp" diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index d4025f4..2d22056 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -9,200 +9,165 @@ #include +#include "_elliptic_impl_pub.hpp" + namespace fc { namespace ecc { namespace detail { - static void init_lib() - { - static int init = init_openssl(); + void _init_lib() { + static int init_o = init_openssl(); } - typedef EC_KEY pub_data_type; - typedef EC_KEY priv_data_type; - - #include "_elliptic_impl.cpp" - - void public_key_impl::free_key() + class private_key_impl { - if( _key != nullptr ) - { - EC_KEY_free(_key); - _key = nullptr; - } - } + public: + private_key_impl() noexcept + { + _init_lib(); + } - EC_KEY* public_key_impl::dup_key( const EC_KEY* cpy ) + private_key_impl( const private_key_impl& cpy ) noexcept + { + _init_lib(); + *this = cpy; + } + + private_key_impl( private_key_impl&& cpy ) noexcept + { + _init_lib(); + *this = cpy; + } + + ~private_key_impl() noexcept + { + free_key(); + } + + private_key_impl& operator=( const private_key_impl& pk ) noexcept + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = EC_KEY_dup( pk._key ); + } else { + EC_KEY_copy( _key, pk._key ); + } + return *this; + } + + private_key_impl& operator=( private_key_impl&& pk ) noexcept + { + if ( this != &pk ) { + free_key(); + _key = pk._key; + pk._key = nullptr; + } + return *this; + } + + EC_KEY* _key = nullptr; + + private: + void free_key() noexcept + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + _key = nullptr; + } + } + }; + } + + private_key::private_key() {} + + private_key::private_key( const private_key& pk ) : my( pk.my ) {} + + private_key::private_key( private_key&& pk ) : my( std::move( pk.my ) ) {} + + private_key::~private_key() {} + + private_key& private_key::operator=( private_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + private_key& private_key::operator=( const private_key& pk ) + { + my = pk.my; + return *this; + } + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + { + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); + } + + int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) + { + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) { - return EC_KEY_dup( cpy ); - } - - void public_key_impl::copy_key( EC_KEY* to, const EC_KEY* from ) - { - EC_KEY_copy( to, from ); + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); } + return self; } - #include "_elliptic_mixed_openssl.cpp" - - /* WARNING! This implementation is broken, it is actually equivalent to - * public_key::add()! - */ -// public_key public_key::mult( const fc::sha256& digest ) const -// { -// // get point from this public key -// const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); -// ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); -// -// ssl_bignum z; -// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); -// -// // multiply by digest -// ssl_bignum one; -// BN_one(one); -// bn_ctx ctx(BN_CTX_new()); -// -// ec_point result(EC_POINT_new(group)); -// EC_POINT_mul(group, result, z, master_pub, one, ctx); -// -// public_key rtn; -// rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); -// EC_KEY_set_public_key(rtn.my->_key,result); -// -// return rtn; -// } - public_key public_key::add( const fc::sha256& digest )const + fc::sha256 private_key::get_secret()const { - try { - ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); - bn_ctx ctx(BN_CTX_new()); - - fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); - - ssl_bignum order; - EC_GROUP_get_order(group, order, ctx); - if( digest_bi > fc::bigint(order) ) - { - FC_THROW_EXCEPTION( exception, "digest > group order" ); - } - - - public_key digest_key = private_key::regenerate(digest).get_public_key(); - const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); - - // get point from this public key - const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); - -// ssl_bignum z; -// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); - - // multiply by digest -// ssl_bignum one; -// BN_one(one); - - ec_point result(EC_POINT_new(group)); - EC_POINT_add(group, result, digest_point, master_pub, ctx); - - if (EC_POINT_is_at_infinity(group, result)) - { - FC_THROW_EXCEPTION( exception, "point at infinity" ); - } - - - public_key rtn; - rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - EC_KEY_set_public_key(rtn.my->_key,result); - return rtn; - } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + return get_secret( my->_key ); } - std::string public_key::to_base58() const + private_key::private_key( EC_KEY* k ) { - public_key_data key = serialize(); - return to_base58( key ); + my->_key = k; } -// signature private_key::sign( const fc::sha256& digest )const -// { -// unsigned int buf_len = ECDSA_size(my->_key); -//// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); -// signature sig; -// assert( buf_len == sizeof(sig) ); -// -// if( !ECDSA_sign( 0, -// (const unsigned char*)&digest, sizeof(digest), -// (unsigned char*)&sig, &buf_len, my->_key ) ) -// { -// FC_THROW_EXCEPTION( exception, "signing error" ); -// } -// -// -// return sig; -// } -// bool public_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) -// { -// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); -// } - - public_key_data public_key::serialize()const - { - public_key_data dat; - if( !my->_key ) return dat; - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); - /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ - /*assert( nbytes == 33 )*/ - char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling - return dat; - /* - EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); - EC_GROUP* group = EC_KEY_get0_group( my->_key ); - EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); - */ - } - public_key_point_data public_key::serialize_ecc_point()const - { - public_key_point_data dat; - if( !my->_key ) return dat; - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); - char* front = &dat.data[0]; - i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling - return dat; - } - - public_key::public_key( const public_key_point_data& dat ) - { - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); - if( !my->_key ) - { - FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); - } - } - } - public_key::public_key( const public_key_data& dat ) - { - const char* front = &dat.data[0]; - if( *front == 0 ){} - else - { - my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); - my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); - if( !my->_key ) - { - FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); - } - } - } - -// bool private_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) -// { -// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); -// } - public_key private_key::get_public_key()const { public_key pub; @@ -221,38 +186,76 @@ namespace fc { namespace ecc { return buf; } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + compact_signature private_key::sign_compact( const fc::sha256& digest )const { - int nV = c.data[0]; - if (nV<27 || nV>=35) - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + try { + FC_ASSERT( my->_key != nullptr ); + auto my_pub_key = get_public_key().serialize(); // just for good measure + //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + public_key_data key_data; + while( true ) + { + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); - ECDSA_SIG *sig = ECDSA_SIG_new(); - BN_bin2bn(&c.data[1],32,sig->r); - BN_bin2bn(&c.data[33],32,sig->s); + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); - if( check_canonical ) - { - FC_ASSERT( is_canonical( c ), "signature is not canonical" ); - } + compact_signature csig; + // memset( csig.data, 0, sizeof(csig) ); - my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + for (int i=0; i<4; i++) + { + if (detail::public_key_impl::ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) + { + unsigned char* buffer = (unsigned char*) key_data.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + if ( key_data == my_pub_key ) + { + nRecId = i; + break; + } + } + } + EC_KEY_free( key ); - if (nV >= 31) - { - EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); - nV -= 4; -// fprintf( stderr, "compressed\n" ); - } - - if (ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) - { - ECDSA_SIG_free(sig); - return; - } - ECDSA_SIG_free(sig); - FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); + if( lenR != 32 ) { free(result); continue; } + if( lenS != 32 ) { free(result); continue; } + //idump( (33-(nBitsR+7)/8) ); + //idump( (65-(nBitsS+7)/8) ); + //idump( (sizeof(csig) ) ); + memcpy( &csig.data[1], &result[4], lenR ); + memcpy( &csig.data[33], &result[6+lenR], lenS ); + //idump( (csig.data[33]) ); + //idump( (csig.data[1]) ); + free(result); + //idump( (nRecId) ); + csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); + /* + idump( (csig) ); + auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); + auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); + idump( (rlen)(slen) ); + */ + } + return csig; + } // while true + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); } } } - -#include "_elliptic_common.cpp" diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 538c716..8cd4044 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -10,102 +10,134 @@ #include #include +#include "_elliptic_impl_priv.hpp" + namespace fc { namespace ecc { namespace detail { - static void init_lib(); - - typedef public_key_data pub_data_type; - typedef private_key_secret priv_data_type; - - #include "_elliptic_impl.cpp" - - void private_key_impl::free_key() - { - if( _key != nullptr ) - { - delete _key; - _key = nullptr; - } + static int init_secp256k1() { + secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); + return 1; } - private_key_secret* private_key_impl::dup_key( const private_key_secret* cpy ) - { - return new private_key_secret( *cpy ); + void _init_lib() { + static int init_s = init_secp256k1(); + static int init_o = init_openssl(); } - void private_key_impl::copy_key( private_key_secret* to, const private_key_secret* from ) + class public_key_impl { - *to = *from; - } + public: + public_key_impl() noexcept + { + _init_lib(); + } + + public_key_impl( const public_key_impl& cpy ) noexcept + : _key( cpy._key ) + { + _init_lib(); + } + + public_key_data _key; + }; } - private_key private_key::regenerate( const fc::sha256& secret ) - { - private_key self; - self.my->_key = new private_key_secret(secret); - return self; - } - - fc::sha256 private_key::get_secret()const - { - if( !my->_key ) - { - return fc::sha256(); - } - return *my->_key; - } - - private_key::private_key( EC_KEY* k ) - { - my->_key = new private_key_secret( get_secret( k ) ); - EC_KEY_free(k); - } - - public_key private_key::get_public_key()const - { - FC_ASSERT( my->_key != nullptr ); - public_key_data pub; - unsigned int pk_len; - FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key->data(), 1 ) ); - FC_ASSERT( pk_len == pub.size() ); - return public_key(pub); - } + static const public_key_data empty_pub; + static const private_key_secret empty_priv; fc::sha512 private_key::get_shared_secret( const public_key& other )const { - FC_ASSERT( my->_key != nullptr ); - FC_ASSERT( other.my->_key != nullptr ); - public_key_data pub(*other.my->_key); - FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key->data() ) ); -// ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); + FC_ASSERT( my->_key != empty_priv ); + FC_ASSERT( other.my->_key != empty_pub ); + public_key_data pub(other.my->_key); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } - static int extended_nonce_function( unsigned char *nonce32, const unsigned char *msg32, - const unsigned char *key32, unsigned int attempt, - const void *data ) { - unsigned int* extra = (unsigned int*) data; - (*extra)++; - return secp256k1_nonce_function_default( nonce32, msg32, key32, *extra, nullptr ); - } - compact_signature private_key::sign_compact( const fc::sha256& digest )const + public_key::~public_key() {} + + public_key::public_key( public_key &&pk ) : my( std::move( pk.my ) ) {} + + public_key& public_key::operator=( const public_key& pk ) { - FC_ASSERT( my->_key != nullptr ); - compact_signature result; - int recid; - unsigned int counter = 0; - do - { - FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key->data(), extended_nonce_function, &counter, &recid )); - } while( !public_key::is_canonical( result ) ); - result.begin()[0] = 27 + 4 + recid; - return result; + my = pk.my; + return *this; } - #include "_elliptic_mixed_secp256k1.cpp" + public_key& public_key::operator=( public_key&& pk ) + { + my = pk.my; + return *this; + } + public_key public_key::add( const fc::sha256& digest )const + { + FC_ASSERT( my->_key != empty_pub ); + public_key_data new_key; + memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + return public_key( new_key ); + } + + std::string public_key::to_base58() const + { + FC_ASSERT( my->_key != empty_pub ); + return to_base58( my->_key ); + } + + public_key_data public_key::serialize()const + { + FC_ASSERT( my->_key != empty_pub ); + return my->_key; + } + + public_key_point_data public_key::serialize_ecc_point()const + { + FC_ASSERT( my->_key != empty_pub ); + public_key_point_data dat; + unsigned int pk_len = my->_key.size(); + memcpy( dat.begin(), my->_key.begin(), pk_len ); + FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); + FC_ASSERT( pk_len == dat.size() ); + return dat; + } + + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + unsigned char* buffer = (unsigned char*) my->_key.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + EC_KEY_free( key ); + } + } + + public_key::public_key( const public_key_data& dat ) + { + my->_key = dat; + } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + unsigned int pk_len; + FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key.begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( pk_len == my->_key.size() ); + } } } - -#include "_elliptic_common.cpp" From 5782fd42aff56a2bc3411d2ed8a7b5fc4ba66956 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 13 Mar 2015 20:18:15 +0100 Subject: [PATCH 32/54] Added missing constructors + pubkey::valid --- src/crypto/elliptic_secp256k1.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 8cd4044..5c1525c 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -56,10 +56,14 @@ namespace fc { namespace ecc { } - public_key::~public_key() {} + public_key::public_key() {} + + public_key::public_key( const public_key &pk ) : my( pk.my ) {} public_key::public_key( public_key &&pk ) : my( std::move( pk.my ) ) {} + public_key::~public_key() {} + public_key& public_key::operator=( const public_key& pk ) { my = pk.my; @@ -72,6 +76,11 @@ namespace fc { namespace ecc { return *this; } + bool public_key::valid()const + { + return my->_key != empty_pub; + } + public_key public_key::add( const fc::sha256& digest )const { FC_ASSERT( my->_key != empty_pub ); From a164a55c8608f5c7052ec1fed43194e1f1a6fa5a Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 24 Mar 2015 17:01:07 +0100 Subject: [PATCH 33/54] Fixed move constructor + assignment --- src/crypto/elliptic_impl_priv.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/elliptic_impl_priv.cpp b/src/crypto/elliptic_impl_priv.cpp index 60c458d..ab009b7 100644 --- a/src/crypto/elliptic_impl_priv.cpp +++ b/src/crypto/elliptic_impl_priv.cpp @@ -33,13 +33,13 @@ namespace fc { namespace ecc { private_key::private_key( const private_key& pk ) : my( pk.my ) {} - private_key::private_key( private_key&& pk ) : my( pk.my ) {} + private_key::private_key( private_key&& pk ) : my( std::move( pk.my ) ) {} private_key::~private_key() {} private_key& private_key::operator=( private_key&& pk ) { - my = pk.my; + my = std::move( pk.my ); return *this; } From d9f6b7a52681b008bac6c5b7bedca841581e2b24 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 7 May 2015 15:59:21 +0200 Subject: [PATCH 34/54] Rebase + upgrade to latest libsecp256k1 API --- src/crypto/_elliptic_impl_priv.hpp | 2 ++ src/crypto/elliptic_impl_priv.cpp | 4 ++-- src/crypto/elliptic_mixed.cpp | 10 +++++----- src/crypto/elliptic_secp256k1.cpp | 16 ++++++++-------- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/crypto/_elliptic_impl_priv.hpp b/src/crypto/_elliptic_impl_priv.hpp index 2effaff..4da6a62 100644 --- a/src/crypto/_elliptic_impl_priv.hpp +++ b/src/crypto/_elliptic_impl_priv.hpp @@ -7,6 +7,8 @@ namespace fc { namespace ecc { namespace detail { + +const secp256k1_context_t* _get_context(); void _init_lib(); class private_key_impl diff --git a/src/crypto/elliptic_impl_priv.cpp b/src/crypto/elliptic_impl_priv.cpp index ab009b7..39e4ceb 100644 --- a/src/crypto/elliptic_impl_priv.cpp +++ b/src/crypto/elliptic_impl_priv.cpp @@ -72,7 +72,7 @@ namespace fc { namespace ecc { FC_ASSERT( my->_key != empty_priv ); public_key_data pub; unsigned int pk_len; - FC_ASSERT( secp256k1_ec_pubkey_create( (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key.data(), 1 ) ); + FC_ASSERT( secp256k1_ec_pubkey_create( detail::_get_context(), (unsigned char*) pub.begin(), (int*) &pk_len, (unsigned char*) my->_key.data(), 1 ) ); FC_ASSERT( pk_len == pub.size() ); return public_key(pub); } @@ -93,7 +93,7 @@ namespace fc { namespace ecc { unsigned int counter = 0; do { - FC_ASSERT( secp256k1_ecdsa_sign_compact( (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key.data(), extended_nonce_function, &counter, &recid )); + FC_ASSERT( secp256k1_ecdsa_sign_compact( detail::_get_context(), (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key.data(), extended_nonce_function, &counter, &recid )); } while( !public_key::is_canonical( result ) ); result.begin()[0] = 27 + 4 + recid; return result; diff --git a/src/crypto/elliptic_mixed.cpp b/src/crypto/elliptic_mixed.cpp index ac6f62d..e9af233 100644 --- a/src/crypto/elliptic_mixed.cpp +++ b/src/crypto/elliptic_mixed.cpp @@ -16,13 +16,13 @@ namespace fc { namespace ecc { namespace detail { - static int init_secp256k1() { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - return 1; + const secp256k1_context_t* _get_context() { + static secp256k1_context_t* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; } void _init_lib() { - static int init_s = init_secp256k1(); + static const secp256k1_context_t* ctx = _get_context(); static int init_o = init_openssl(); } } @@ -33,7 +33,7 @@ namespace fc { namespace ecc { FC_ASSERT( my->_key != empty_priv ); FC_ASSERT( other.my->_key != nullptr ); public_key_data pub(other.serialize()); - FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( detail::_get_context(), (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 5c1525c..0e6484b 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -15,13 +15,13 @@ namespace fc { namespace ecc { namespace detail { - static int init_secp256k1() { - secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN); - return 1; + const secp256k1_context_t* _get_context() { + static secp256k1_context_t* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; } void _init_lib() { - static int init_s = init_secp256k1(); + static const secp256k1_context_t* ctx = _get_context(); static int init_o = init_openssl(); } @@ -51,7 +51,7 @@ namespace fc { namespace ecc { FC_ASSERT( my->_key != empty_priv ); FC_ASSERT( other.my->_key != empty_pub ); public_key_data pub(other.my->_key); - FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( detail::_get_context(), (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); } @@ -86,7 +86,7 @@ namespace fc { namespace ecc { FC_ASSERT( my->_key != empty_pub ); public_key_data new_key; memcpy( new_key.begin(), my->_key.begin(), new_key.size() ); - FC_ASSERT( secp256k1_ec_pubkey_tweak_add( (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_add( detail::_get_context(), (unsigned char*) new_key.begin(), new_key.size(), (unsigned char*) digest.data() ) ); return public_key( new_key ); } @@ -108,7 +108,7 @@ namespace fc { namespace ecc { public_key_point_data dat; unsigned int pk_len = my->_key.size(); memcpy( dat.begin(), my->_key.begin(), pk_len ); - FC_ASSERT( secp256k1_ec_pubkey_decompress( (unsigned char *) dat.begin(), (int*) &pk_len ) ); + FC_ASSERT( secp256k1_ec_pubkey_decompress( detail::_get_context(), (unsigned char *) dat.begin(), (int*) &pk_len ) ); FC_ASSERT( pk_len == dat.size() ); return dat; } @@ -146,7 +146,7 @@ namespace fc { namespace ecc { } unsigned int pk_len; - FC_ASSERT( secp256k1_ecdsa_recover_compact( (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key.begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); + FC_ASSERT( secp256k1_ecdsa_recover_compact( detail::_get_context(), (unsigned char*) digest.data(), (unsigned char*) c.begin() + 1, (unsigned char*) my->_key.begin(), (int*) &pk_len, 1, (*c.begin() - 27) & 3 ) ); FC_ASSERT( pk_len == my->_key.size() ); } } } From 4f0919c49365a7799f5f40549e646d06b5a51ca3 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 12:58:47 -0400 Subject: [PATCH 35/54] adding sanity checks to string->json parsing --- CMakeLists.txt | 3 +++ src/io/json.cpp | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b68d841..4e75f58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,9 @@ add_executable( task_cancel_test tests/task_cancel.cpp ) target_link_libraries( task_cancel_test fc ) +add_executable( bloom_test tests/bloom_test.cpp ) +target_link_libraries( bloom_test fc ) + add_executable( real128_test tests/real128_test.cpp ) target_link_libraries( real128_test fc ) diff --git a/src/io/json.cpp b/src/io/json.cpp index 70a572c..505e7ca 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -436,9 +436,30 @@ namespace fc } return variant(); } + + + /** the purpose of this check is to verify that we will not get a stack overflow in the recursive descent parser */ + void check_string_depth( const string& utf8_str ) + { + int32_t open_object = 0; + int32_t open_array = 0; + for( auto c : utf8_str ) + { + switch( c ) + { + case '{': open_object++; break; + case '}': open_object--; break; + case '[': open_array++; break; + case ']': open_array--; break; + } + FC_ASSERT( open_object < 100 && open_array < 100, "object graph too deep", ("object depth",open_object)("array depth", open_array) ); + } + } variant json::from_string( const std::string& utf8_str, parse_type ptype ) { try { + check_string_depth( utf8_str ); + fc::stringstream in( utf8_str ); //in.exceptions( std::ifstream::eofbit ); switch( ptype ) @@ -456,6 +477,7 @@ namespace fc variants json::variants_from_string( const std::string& utf8_str, parse_type ptype ) { try { + check_string_depth( utf8_str ); variants result; fc::stringstream in( utf8_str ); //in.exceptions( std::ifstream::eofbit ); From 812b42dbe601d22a36d7466e1c72a9aa38cebf21 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 12:59:14 -0400 Subject: [PATCH 36/54] adding default --- src/io/json.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/io/json.cpp b/src/io/json.cpp index 505e7ca..2a116b8 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -451,6 +451,7 @@ namespace fc case '}': open_object--; break; case '[': open_array++; break; case ']': open_array--; break; + default: break; } FC_ASSERT( open_object < 100 && open_array < 100, "object graph too deep", ("object depth",open_object)("array depth", open_array) ); } From be5a67763b78896a8de7335f35107965ad706b99 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 12 May 2015 13:05:04 -0400 Subject: [PATCH 37/54] Add new min and max methods on safe --- include/fc/safe.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/fc/safe.hpp b/include/fc/safe.hpp index bb86a71..0c1a12e 100644 --- a/include/fc/safe.hpp +++ b/include/fc/safe.hpp @@ -22,6 +22,10 @@ namespace fc { safe(){} safe( const safe& o ):value(o.value){} + static safe max() + { return std::numeric_limits::max(); } + static safe min() + { return std::numeric_limits::min(); } safe& operator += ( const safe& b ) { From 4894219fd685d9e8bb51290cef44c05d7498b0b6 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 13:07:56 -0400 Subject: [PATCH 38/54] adding bloom test --- tests/bloom_test.cpp | 141 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/bloom_test.cpp diff --git a/tests/bloom_test.cpp b/tests/bloom_test.cpp new file mode 100644 index 0000000..2e9adb5 --- /dev/null +++ b/tests/bloom_test.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace fc; + +int main( int argc, char** argv ) +{ + try { + + bloom_parameters parameters; + + // How many elements roughly do we expect to insert? + parameters.projected_element_count = 100000; + + // Maximum tolerable false positive probability? (0,1) + parameters.false_positive_probability = 0.0001; // 1 in 10000 + + // Simple randomizer (optional) + parameters.random_seed = 0xA5A5A5A5; + + if (!parameters) + { + std::cout << "Error - Invalid set of bloom filter parameters!" << std::endl; + return 1; + } + + parameters.compute_optimal_parameters(); + + //Instantiate Bloom Filter + bloom_filter filter(parameters); + + if( argc > 1 ) + { + uint32_t count = 0; + std::string line; + std::ifstream in(argv[1]); + std::ofstream words("words.txt"); + while( !in.eof() && count < 100000 ) + { + std::getline(in, line); + std::cout << "'"< -100; --i) + { + if (filter.contains(i)) + { + std::cout << "BF falsely contains: " << i << std::endl; + } + } + } + + wdump((filter)); + auto packed_filter = fc::raw::pack(filter); + wdump((packed_filter.size())); + wdump((packed_filter)); + + return 0; + } + catch ( const fc::exception& e ) + { + edump((e.to_detail_string()) ); + } +} From fb62b6421cc9a265a0173cbc1eb947cc9e51b46c Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 14:50:08 -0400 Subject: [PATCH 39/54] adding ssl support to websocket --- include/fc/network/http/websocket.hpp | 26 ++- include/fc/rpc/websocket_api.hpp | 18 +- src/network/http/websocket.cpp | 246 +++++++++++++++++++++++++- 3 files changed, 283 insertions(+), 7 deletions(-) diff --git a/include/fc/network/http/websocket.hpp b/include/fc/network/http/websocket.hpp index 5c253aa..3df3c90 100644 --- a/include/fc/network/http/websocket.hpp +++ b/include/fc/network/http/websocket.hpp @@ -9,6 +9,7 @@ namespace fc { namespace http { namespace detail { class websocket_server_impl; + class websocket_tls_server_impl; class websocket_client_impl; } // namespace detail; @@ -19,16 +20,19 @@ namespace fc { namespace http { virtual void send_message( const std::string& message ) = 0; virtual void close( int64_t code, const std::string& reason ){}; void on_message( const std::string& message ) { _on_message(message); } + string on_http( const std::string& message ) { return _on_http(message); } void on_message_handler( const std::function& h ) { _on_message = h; } + void on_http_handler( const std::function& h ) { _on_http = h; } void set_session_data( fc::any d ){ _session_data = std::move(d); } fc::any& get_session_data() { return _session_data; } fc::signal closed; private: - fc::any _session_data; - std::function _on_message; + fc::any _session_data; + std::function _on_message; + std::function _on_http; }; typedef std::shared_ptr websocket_connection_ptr; @@ -50,6 +54,24 @@ namespace fc { namespace http { std::unique_ptr my; }; + + class websocket_tls_server + { + public: + websocket_tls_server( const std::string& server_pem = std::string(), + const std::string& ssl_password = std::string()); + ~websocket_tls_server(); + + void on_connection( const on_connection_handler& handler); + void listen( uint16_t port ); + void listen( const fc::ip::endpoint& ep ); + void start_accept(); + + private: + friend class detail::websocket_tls_server_impl; + std::unique_ptr my; + }; + class websocket_client { public: diff --git a/include/fc/rpc/websocket_api.hpp b/include/fc/rpc/websocket_api.hpp index b4ab6c5..7280298 100644 --- a/include/fc/rpc/websocket_api.hpp +++ b/include/fc/rpc/websocket_api.hpp @@ -40,7 +40,8 @@ namespace fc { namespace rpc { return this->receive_call( 0, method_name, args ); }); - _connection.on_message_handler( [&]( const std::string& msg ){ on_message(msg); } ); + _connection.on_message_handler( [&]( const std::string& msg ){ on_message(msg,true); } ); + _connection.on_http_handler( [&]( const std::string& msg ){ return on_message(msg,false); } ); _connection.closed.connect( [this](){ closed(); } ); } @@ -66,7 +67,7 @@ namespace fc { namespace rpc { protected: - void on_message( const std::string& message ) + std::string on_message( const std::string& message, bool send_message = true ) { try { auto var = fc::json::from_string(message); @@ -78,14 +79,21 @@ namespace fc { namespace rpc { auto result = _rpc_state.local_call( call.method, call.params ); if( call.id ) { - _connection.send_message( fc::json::to_string( response( *call.id, result ) ) ); + auto reply = fc::json::to_string( response( *call.id, result ) ); + if( send_message ) + _connection.send_message( reply ); + return reply; } } catch ( const fc::exception& e ) { if( call.id ) { - _connection.send_message( fc::json::to_string( response( *call.id, error_object{ 1, e.to_detail_string(), fc::variant(e)} ) ) ); + auto reply = fc::json::to_string( response( *call.id, error_object{ 1, e.to_detail_string(), fc::variant(e)} ) ); + if( send_message ) + _connection.send_message( reply ); + + return reply; } } } @@ -96,7 +104,9 @@ namespace fc { namespace rpc { } } catch ( const fc::exception& e ) { wdump((e.to_detail_string())); + return e.to_detail_string(); } + return string(); } fc::http::websocket_connection& _connection; fc::rpc::state _rpc_state; diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 0250c8a..63ccd89 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -57,12 +57,87 @@ namespace fc { namespace http { static const long timeout_open_handshake = 0; }; + struct asio_tls_with_stub_log : public websocketpp::config::asio_tls { + + typedef asio_with_stub_log type; + typedef asio_tls base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + /// Custom Logging policies + /*typedef websocketpp::log::syslog elog_type; + typedef websocketpp::log::syslog alog_type; + */ + //typedef base::alog_type alog_type; + //typedef base::elog_type elog_type; + typedef websocketpp::log::stub elog_type; + typedef websocketpp::log::stub alog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; + + static const long timeout_open_handshake = 0; + }; + struct asio_tls_stub_log : public websocketpp::config::asio_tls { + typedef asio_tls_stub_log type; + typedef asio_tls base; + + typedef base::concurrency_type concurrency_type; + + typedef base::request_type request_type; + typedef base::response_type response_type; + + typedef base::message_type message_type; + typedef base::con_msg_manager_type con_msg_manager_type; + typedef base::endpoint_msg_manager_type endpoint_msg_manager_type; + + //typedef base::alog_type alog_type; + //typedef base::elog_type elog_type; + typedef websocketpp::log::stub elog_type; + typedef websocketpp::log::stub alog_type; + + typedef base::rng_type rng_type; + + struct transport_config : public base::transport_config { + typedef type::concurrency_type concurrency_type; + typedef type::alog_type alog_type; + typedef type::elog_type elog_type; + typedef type::request_type request_type; + typedef type::response_type response_type; + typedef websocketpp::transport::asio::tls_socket::endpoint socket_type; + }; + + typedef websocketpp::transport::asio::endpoint + transport_type; + }; + using websocketpp::connection_hdl; - typedef websocketpp::server websocket_server_type; + typedef websocketpp::server websocket_server_type; + typedef websocketpp::server websocket_tls_server_type; template class websocket_connection_impl : public websocket_connection @@ -90,12 +165,15 @@ namespace fc { namespace http { T _ws_connection; }; + typedef websocketpp::lib::shared_ptr context_ptr; + class websocket_server_impl { public: websocket_server_impl() :_server_thread( fc::thread::current() ) { + _server.clear_access_channels( websocketpp::log::alevel::all ); _server.init_asio(&fc::asio::default_io_service()); _server.set_reuse_addr(true); @@ -113,6 +191,24 @@ namespace fc { namespace http { current_con->second->on_message( msg->get_payload() ); }).wait(); }); + + _server.set_http_handler( [&]( connection_hdl hdl ){ + _server_thread.async( [&](){ + + auto current_con = std::make_shared>( _server.get_con_from_hdl(hdl) ); + _on_connection( current_con ); + + auto con = _server.get_con_from_hdl(hdl); + wdump(("server")(con->get_request_body())); + auto response = current_con->on_http( con->get_request_body() ); + + con->set_body( response ); + con->set_status( websocketpp::http::status_code::ok ); + current_con->closed(); + + }).wait(); + }); + _server.set_close_handler( [&]( connection_hdl hdl ){ _server_thread.async( [&](){ _connections[hdl]->closed(); @@ -148,6 +244,121 @@ namespace fc { namespace http { fc::promise::ptr _closed; }; + class websocket_tls_server_impl + { + public: + websocket_tls_server_impl( const string& server_pem, const string& ssl_password ) + :_server_thread( fc::thread::current() ) + { + //if( server_pem.size() ) + { + _server.set_tls_init_handler( [=]( websocketpp::connection_hdl hdl ) -> context_ptr { + context_ptr ctx = websocketpp::lib::make_shared(boost::asio::ssl::context::tlsv1); + try { + ctx->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::single_dh_use); + ctx->set_password_callback([=](std::size_t max_length, boost::asio::ssl::context::password_purpose){ return ssl_password;}); + ctx->use_certificate_chain_file(server_pem); + ctx->use_private_key_file(server_pem, boost::asio::ssl::context::pem); + } catch (std::exception& e) { + std::cout << e.what() << std::endl; + } + return ctx; + }); + } + + _server.clear_access_channels( websocketpp::log::alevel::all ); + _server.init_asio(&fc::asio::default_io_service()); + _server.set_reuse_addr(true); + _server.set_open_handler( [&]( connection_hdl hdl ){ + _server_thread.async( [&](){ + auto new_con = std::make_shared>( _server.get_con_from_hdl(hdl) ); + _on_connection( _connections[hdl] = new_con ); + }).wait(); + }); + _server.set_message_handler( [&]( connection_hdl hdl, websocket_server_type::message_ptr msg ){ + _server_thread.async( [&](){ + auto current_con = _connections.find(hdl); + assert( current_con != _connections.end() ); + wdump(("server")(msg->get_payload())); + current_con->second->on_message( msg->get_payload() ); + }).wait(); + }); + + _server.set_http_handler( [&]( connection_hdl hdl ){ + _server_thread.async( [&](){ + + auto current_con = std::make_shared>( _server.get_con_from_hdl(hdl) ); + try{ + _on_connection( current_con ); + + auto con = _server.get_con_from_hdl(hdl); + wdump(("server")(con->get_request_body())); + auto response = current_con->on_http( con->get_request_body() ); + + con->set_body( response ); + con->set_status( websocketpp::http::status_code::ok ); + } catch ( const fc::exception& e ) + { + edump((e.to_detail_string())); + } + current_con->closed(); + + }).wait(); + }); + + _server.set_close_handler( [&]( connection_hdl hdl ){ + _server_thread.async( [&](){ + _connections[hdl]->closed(); + _connections.erase( hdl ); + }).wait(); + }); + + _server.set_fail_handler( [&]( connection_hdl hdl ){ + if( _server.is_listening() ) + { + _server_thread.async( [&](){ + if( _connections.find(hdl) != _connections.end() ) + { + _connections[hdl]->closed(); + _connections.erase( hdl ); + } + }).wait(); + } + }); + } + ~websocket_tls_server_impl() + { + if( _server.is_listening() ) + _server.stop_listening(); + auto cpy_con = _connections; + for( auto item : cpy_con ) + _server.close( item.first, 0, "server exit" ); + } + + typedef std::map > con_map; + + con_map _connections; + fc::thread& _server_thread; + websocket_tls_server_type _server; + on_connection_handler _on_connection; + fc::promise::ptr _closed; + }; + + + + + + + + + + + + + typedef websocketpp::client websocket_client_type; typedef websocket_client_type::connection_ptr websocket_client_connection_type; @@ -220,6 +431,39 @@ namespace fc { namespace http { my->_server.start_accept(); } + + + + websocket_tls_server::websocket_tls_server( const string& server_pem, const string& ssl_password ):my( new detail::websocket_tls_server_impl(server_pem, ssl_password) ) {} + websocket_tls_server::~websocket_tls_server(){} + + void websocket_tls_server::on_connection( const on_connection_handler& handler ) + { + my->_on_connection = handler; + } + + void websocket_tls_server::listen( uint16_t port ) + { + my->_server.listen(port); + } + void websocket_tls_server::listen( const fc::ip::endpoint& ep ) + { + my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); + } + + void websocket_tls_server::start_accept() { + my->_server.start_accept(); + } + + + + + + + + + + websocket_client::websocket_client():my( new detail::websocket_client_impl() ) {} websocket_client::~websocket_client(){ } websocket_connection_ptr websocket_client::connect( const std::string& uri ) From c28ed38f1ad9487c8e1ca4593b2d7e845d055aa6 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 16:16:10 -0400 Subject: [PATCH 40/54] adding support for secure websocket client --- include/fc/network/http/websocket.hpp | 13 +++ src/network/http/websocket.cpp | 156 ++++++++++++++++++++++++-- 2 files changed, 158 insertions(+), 11 deletions(-) diff --git a/include/fc/network/http/websocket.hpp b/include/fc/network/http/websocket.hpp index 3df3c90..7b55930 100644 --- a/include/fc/network/http/websocket.hpp +++ b/include/fc/network/http/websocket.hpp @@ -11,6 +11,7 @@ namespace fc { namespace http { class websocket_server_impl; class websocket_tls_server_impl; class websocket_client_impl; + class websocket_tls_client_impl; } // namespace detail; class websocket_connection @@ -79,8 +80,20 @@ namespace fc { namespace http { ~websocket_client(); websocket_connection_ptr connect( const std::string& uri ); + websocket_connection_ptr secure_connect( const std::string& uri ); private: std::unique_ptr my; + std::unique_ptr smy; + }; + class websocket_tls_client + { + public: + websocket_tls_client(); + ~websocket_tls_client(); + + websocket_connection_ptr connect( const std::string& uri ); + private: + std::unique_ptr my; }; } } diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 63ccd89..a65bae8 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -136,7 +136,7 @@ namespace fc { namespace http { using websocketpp::connection_hdl; - typedef websocketpp::server websocket_server_type; + typedef websocketpp::server websocket_server_type; typedef websocketpp::server websocket_tls_server_type; template @@ -211,8 +211,15 @@ namespace fc { namespace http { _server.set_close_handler( [&]( connection_hdl hdl ){ _server_thread.async( [&](){ - _connections[hdl]->closed(); - _connections.erase( hdl ); + if( _connections.find(hdl) != _connections.end() ) + { + _connections[hdl]->closed(); + _connections.erase( hdl ); + } + else + { + wlog( "unknown connection closed" ); + } }).wait(); }); @@ -220,8 +227,15 @@ namespace fc { namespace http { if( _server.is_listening() ) { _server_thread.async( [&](){ - _connections[hdl]->closed(); - _connections.erase( hdl ); + if( _connections.find(hdl) != _connections.end() ) + { + _connections[hdl]->closed(); + _connections.erase( hdl ); + } + else + { + wlog( "unknown connection failed" ); + } }).wait(); } }); @@ -360,7 +374,10 @@ namespace fc { namespace http { typedef websocketpp::client websocket_client_type; + typedef websocketpp::client websocket_tls_client_type; + typedef websocket_client_type::connection_ptr websocket_client_connection_type; + typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type; class websocket_client_impl { @@ -392,6 +409,7 @@ namespace fc { namespace http { if( _closed ) _closed->set_value(); }); + _client.init_asio( &fc::asio::default_io_service() ); } ~websocket_client_impl() @@ -408,6 +426,72 @@ namespace fc { namespace http { websocket_client_type _client; websocket_connection_ptr _connection; }; + + + + class websocket_tls_client_impl + { + public: + typedef websocket_tls_client_type::message_ptr message_ptr; + + websocket_tls_client_impl() + :_client_thread( fc::thread::current() ) + { + _client.clear_access_channels( websocketpp::log::alevel::all ); + _client.set_message_handler( [&]( connection_hdl hdl, message_ptr msg ){ + _client_thread.async( [&](){ + wdump((msg->get_payload())); + _connection->on_message( msg->get_payload() ); + }).wait(); + }); + _client.set_close_handler( [=]( connection_hdl hdl ){ + if( _connection ) + _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); + if( _closed ) _closed->set_value(); + }); + _client.set_fail_handler( [=]( connection_hdl hdl ){ + auto con = _client.get_con_from_hdl(hdl); + auto message = con->get_ec().message(); + if( _connection ) + _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); + if( _connected && !_connected->ready() ) + _connected->set_exception( exception_ptr( new FC_EXCEPTION( exception, "${message}", ("message",message)) ) ); + if( _closed ) + _closed->set_value(); + }); + + _client.set_tls_init_handler( [=](websocketpp::connection_hdl) { + context_ptr ctx = websocketpp::lib::make_shared(boost::asio::ssl::context::tlsv1); + try { + ctx->set_options(boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::no_sslv3 | + boost::asio::ssl::context::single_dh_use); + } catch (std::exception& e) { + edump((e.what())); + std::cout << e.what() << std::endl; + } + return ctx; + }); + + _client.init_asio( &fc::asio::default_io_service() ); + } + ~websocket_tls_client_impl() + { + if(_connection ) + { + _connection->close(0, "client closed"); + _closed->wait(); + } + } + fc::promise::ptr _connected; + fc::promise::ptr _closed; + fc::thread& _client_thread; + websocket_tls_client_type _client; + websocket_connection_ptr _connection; + }; + + } // namespace detail websocket_server::websocket_server():my( new detail::websocket_server_impl() ) {} @@ -456,18 +540,20 @@ namespace fc { namespace http { } + websocket_tls_client::websocket_tls_client():my( new detail::websocket_tls_client_impl() ) {} + websocket_tls_client::~websocket_tls_client(){ } - - - - - - websocket_client::websocket_client():my( new detail::websocket_client_impl() ) {} + websocket_client::websocket_client():my( new detail::websocket_client_impl() ),smy(new detail::websocket_tls_client_impl()) {} websocket_client::~websocket_client(){ } + websocket_connection_ptr websocket_client::connect( const std::string& uri ) { try { + if( uri.substr(0,4) == "wss:" ) + return secure_connect(uri); + FC_ASSERT( uri.substr(0,3) == "ws:" ); + // wlog( "connecting to ${uri}", ("uri",uri)); websocketpp::lib::error_code ec; @@ -480,6 +566,54 @@ namespace fc { namespace http { my->_connected->set_value(); }); + auto con = my->_client.get_connection( uri, ec ); + + if( ec ) FC_ASSERT( !ec, "error: ${e}", ("e",ec.message()) ); + + my->_client.connect(con); + my->_connected->wait(); + return my->_connection; + } FC_CAPTURE_AND_RETHROW( (uri) ) } + + websocket_connection_ptr websocket_client::secure_connect( const std::string& uri ) + { try { + if( uri.substr(0,3) == "ws:" ) + return connect(uri); + FC_ASSERT( uri.substr(0,4) == "wss:" ); + // wlog( "connecting to ${uri}", ("uri",uri)); + websocketpp::lib::error_code ec; + + smy->_connected = fc::promise::ptr( new fc::promise("websocket::connect") ); + + smy->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){ + auto con = smy->_client.get_con_from_hdl(hdl); + smy->_connection = std::make_shared>( con ); + smy->_closed = fc::promise::ptr( new fc::promise("websocket::closed") ); + smy->_connected->set_value(); + }); + + auto con = smy->_client.get_connection( uri, ec ); + if( ec ) + FC_ASSERT( !ec, "error: ${e}", ("e",ec.message()) ); + smy->_client.connect(con); + smy->_connected->wait(); + return smy->_connection; + } FC_CAPTURE_AND_RETHROW( (uri) ) } + + websocket_connection_ptr websocket_tls_client::connect( const std::string& uri ) + { try { + // wlog( "connecting to ${uri}", ("uri",uri)); + websocketpp::lib::error_code ec; + + my->_connected = fc::promise::ptr( new fc::promise("websocket::connect") ); + + my->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){ + auto con = my->_client.get_con_from_hdl(hdl); + my->_connection = std::make_shared>( con ); + my->_closed = fc::promise::ptr( new fc::promise("websocket::closed") ); + my->_connected->set_value(); + }); + auto con = my->_client.get_connection( uri, ec ); if( ec ) { From 9ef91e2245dc837acf9125d4b4bdea09d91ffcaf Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 17:15:00 -0400 Subject: [PATCH 41/54] partial fixes to crashing on websocket client close --- include/fc/network/http/websocket.hpp | 2 +- src/network/http/websocket.cpp | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/fc/network/http/websocket.hpp b/include/fc/network/http/websocket.hpp index 7b55930..1969e09 100644 --- a/include/fc/network/http/websocket.hpp +++ b/include/fc/network/http/websocket.hpp @@ -17,7 +17,7 @@ namespace fc { namespace http { class websocket_connection { public: - virtual ~websocket_connection(){}; + virtual ~websocket_connection(){ wlog("."); }; virtual void send_message( const std::string& message ) = 0; virtual void close( int64_t code, const std::string& reason ){}; void on_message( const std::string& message ) { _on_message(message); } diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index a65bae8..2bf7a60 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -145,10 +145,12 @@ namespace fc { namespace http { public: websocket_connection_impl( T con ) :_ws_connection(con){ + wdump((uint64_t(this))); } ~websocket_connection_impl() { + wdump((uint64_t(this))); } virtual void send_message( const std::string& message )override @@ -446,10 +448,23 @@ namespace fc { namespace http { }); _client.set_close_handler( [=]( connection_hdl hdl ){ if( _connection ) - _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); - if( _closed ) _closed->set_value(); + { + try { + _client_thread.async( [&](){ + wlog(". ${p}", ("p",uint64_t(_connection.get()))); + if( !_shutting_down && !_closed && _connection ) + _connection->closed(); + _connection.reset(); + } ).wait(); + } catch ( const fc::exception& e ) + { + if( _closed ) _closed->set_exception( e.dynamic_copy_exception() ); + } + if( _closed ) _closed->set_value(); + } }); _client.set_fail_handler( [=]( connection_hdl hdl ){ + elog( "." ); auto con = _client.get_con_from_hdl(hdl); auto message = con->get_ec().message(); if( _connection ) @@ -480,10 +495,13 @@ namespace fc { namespace http { { if(_connection ) { + wlog("."); + _shutting_down = true; _connection->close(0, "client closed"); _closed->wait(); } } + bool _shutting_down = false; fc::promise::ptr _connected; fc::promise::ptr _closed; fc::thread& _client_thread; From e934e9a9f780fe917b566aec5d1508a1f49cf924 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 12 May 2015 18:41:40 -0400 Subject: [PATCH 42/54] Adding missing file --- include/fc/bloom_filter.hpp | 621 ++++++++++++++++++++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 include/fc/bloom_filter.hpp diff --git a/include/fc/bloom_filter.hpp b/include/fc/bloom_filter.hpp new file mode 100644 index 0000000..67aa5cb --- /dev/null +++ b/include/fc/bloom_filter.hpp @@ -0,0 +1,621 @@ +#pragma once + +/* + ********************************************************************* + * * + * Open Bloom Filter * + * * + * Author: Arash Partow - 2000 * + * URL: http://www.partow.net * + * URL: http://www.partow.net/programming/hashfunctions/index.html * + * * + * Copyright notice: * + * Free use of the Open Bloom Filter Library is permitted under the * + * guidelines and in accordance with the most current version of the * + * Common Public License. * + * http://www.opensource.org/licenses/cpl1.0.php * + * * + ********************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace fc { + + +static const std::size_t bits_per_char = 0x08; // 8 bits in 1 char(unsigned) +static const unsigned char bit_mask[bits_per_char] = { + 0x01, //00000001 + 0x02, //00000010 + 0x04, //00000100 + 0x08, //00001000 + 0x10, //00010000 + 0x20, //00100000 + 0x40, //01000000 + 0x80 //10000000 + }; + +class bloom_parameters +{ +public: + + bloom_parameters() + : minimum_size(1), + maximum_size(std::numeric_limits::max()), + minimum_number_of_hashes(1), + maximum_number_of_hashes(std::numeric_limits::max()), + projected_element_count(10000), + false_positive_probability(1.0 / projected_element_count), + random_seed(0xA5A5A5A55A5A5A5AULL) + {} + + virtual ~bloom_parameters() + {} + + inline bool operator!() + { + return (minimum_size > maximum_size) || + (minimum_number_of_hashes > maximum_number_of_hashes) || + (minimum_number_of_hashes < 1) || + (0 == maximum_number_of_hashes) || + (0 == projected_element_count) || + (false_positive_probability < 0.0) || + (std::numeric_limits::infinity() == std::abs(false_positive_probability)) || + (0 == random_seed) || + (0xFFFFFFFFFFFFFFFFULL == random_seed); + } + + //Allowed min/max size of the bloom filter in bits + unsigned long long int minimum_size; + unsigned long long int maximum_size; + + //Allowed min/max number of hash functions + unsigned int minimum_number_of_hashes; + unsigned int maximum_number_of_hashes; + + //The approximate number of elements to be inserted + //into the bloom filter, should be within one order + //of magnitude. The default is 10000. + unsigned long long int projected_element_count; + + //The approximate false positive probability expected + //from the bloom filter. The default is the reciprocal + //of the projected_element_count. + double false_positive_probability; + + unsigned long long int random_seed; + + struct optimal_parameters_t + { + optimal_parameters_t() + : number_of_hashes(0), + table_size(0) + {} + + unsigned int number_of_hashes; + unsigned long long int table_size; + }; + + optimal_parameters_t optimal_parameters; + + virtual bool compute_optimal_parameters() + { + /* + Note: + The following will attempt to find the number of hash functions + and minimum amount of storage bits required to construct a bloom + filter consistent with the user defined false positive probability + and estimated element insertion count. + */ + + if (!(*this)) + return false; + + double min_m = std::numeric_limits::infinity(); + double min_k = 0.0; + double curr_m = 0.0; + double k = 1.0; + + while (k < 1000.0) + { + double numerator = (- k * projected_element_count); + double denominator = std::log(1.0 - std::pow(false_positive_probability, 1.0 / k)); + curr_m = numerator / denominator; + if (curr_m < min_m) + { + min_m = curr_m; + min_k = k; + } + k += 1.0; + } + + optimal_parameters_t& optp = optimal_parameters; + + optp.number_of_hashes = static_cast(min_k); + optp.table_size = static_cast(min_m); + optp.table_size += (((optp.table_size % bits_per_char) != 0) ? (bits_per_char - (optp.table_size % bits_per_char)) : 0); + + if (optp.number_of_hashes < minimum_number_of_hashes) + optp.number_of_hashes = minimum_number_of_hashes; + else if (optp.number_of_hashes > maximum_number_of_hashes) + optp.number_of_hashes = maximum_number_of_hashes; + + if (optp.table_size < minimum_size) + optp.table_size = minimum_size; + else if (optp.table_size > maximum_size) + optp.table_size = maximum_size; + + return true; + } + +}; + +class bloom_filter +{ +protected: + + typedef unsigned int bloom_type; + typedef unsigned char cell_type; + +public: + + bloom_filter() + : salt_count_(0), + table_size_(0), + raw_table_size_(0), + projected_element_count_(0), + inserted_element_count_(0), + random_seed_(0), + desired_false_positive_probability_(0.0) + {} + + bloom_filter(const bloom_parameters& p) + : projected_element_count_(p.projected_element_count), + inserted_element_count_(0), + random_seed_((p.random_seed * 0xA5A5A5A5) + 1), + desired_false_positive_probability_(p.false_positive_probability) + { + salt_count_ = p.optimal_parameters.number_of_hashes; + table_size_ = p.optimal_parameters.table_size; + generate_unique_salt(); + raw_table_size_ = table_size_ / bits_per_char; + + bit_table_.resize( static_cast(raw_table_size_) ); + //bit_table_ = new cell_type[static_cast(raw_table_size_)]; + std::fill_n(bit_table_.data(),raw_table_size_,0x00); + } + + bloom_filter(const bloom_filter& filter) + { + this->operator=(filter); + } + + inline bool operator == (const bloom_filter& f) const + { + if (this != &f) + { + return + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (raw_table_size_ == f.raw_table_size_) && + (projected_element_count_ == f.projected_element_count_) && + (inserted_element_count_ == f.inserted_element_count_) && + (random_seed_ == f.random_seed_) && + (desired_false_positive_probability_ == f.desired_false_positive_probability_) && + (salt_ == f.salt_) && + std::equal(f.bit_table_.data(),f.bit_table_.data() + raw_table_size_,bit_table_.data()); + } + else + return true; + } + + inline bool operator != (const bloom_filter& f) const + { + return !operator==(f); + } + + inline bloom_filter& operator = (const bloom_filter& f) + { + if (this != &f) + { + salt_count_ = f.salt_count_; + table_size_ = f.table_size_; + raw_table_size_ = f.raw_table_size_; + projected_element_count_ = f.projected_element_count_; + inserted_element_count_ = f.inserted_element_count_; + random_seed_ = f.random_seed_; + desired_false_positive_probability_ = f.desired_false_positive_probability_; + bit_table_.resize( raw_table_size_ ); + std::copy(f.bit_table_.data(),f.bit_table_.data() + raw_table_size_,bit_table_.data()); + salt_ = f.salt_; + } + return *this; + } + + virtual ~bloom_filter() + { + } + + inline bool operator!() const + { + return (0 == table_size_); + } + + inline void clear() + { + std::fill_n(bit_table_.data(),raw_table_size_,0x00); + inserted_element_count_ = 0; + } + + inline void insert(const unsigned char* key_begin, const std::size_t& length) + { + std::size_t bit_index = 0; + std::size_t bit = 0; + for (std::size_t i = 0; i < salt_.size(); ++i) + { + compute_indices(hash_ap(key_begin,length,salt_[i]),bit_index,bit); + bit_table_[bit_index / bits_per_char] |= bit_mask[bit]; + } + ++inserted_element_count_; + } + + template + inline void insert(const T& t) + { + // Note: T must be a C++ POD type. + insert(reinterpret_cast(&t),sizeof(T)); + } + + inline void insert(const std::string& key) + { + insert(reinterpret_cast(key.c_str()),key.size()); + } + + inline void insert(const char* data, const std::size_t& length) + { + insert(reinterpret_cast(data),length); + } + + template + inline void insert(const InputIterator begin, const InputIterator end) + { + InputIterator itr = begin; + while (end != itr) + { + insert(*(itr++)); + } + } + + inline virtual bool contains(const unsigned char* key_begin, const std::size_t length) const + { + std::size_t bit_index = 0; + std::size_t bit = 0; + for (std::size_t i = 0; i < salt_.size(); ++i) + { + compute_indices(hash_ap(key_begin,length,salt_[i]),bit_index,bit); + if ((bit_table_[bit_index / bits_per_char] & bit_mask[bit]) != bit_mask[bit]) + { + return false; + } + } + return true; + } + + template + inline bool contains(const T& t) const + { + return contains(reinterpret_cast(&t),static_cast(sizeof(T))); + } + + inline bool contains(const std::string& key) const + { + return contains(reinterpret_cast(key.c_str()),key.size()); + } + + inline bool contains(const char* data, const std::size_t& length) const + { + return contains(reinterpret_cast(data),length); + } + + template + inline InputIterator contains_all(const InputIterator begin, const InputIterator end) const + { + InputIterator itr = begin; + while (end != itr) + { + if (!contains(*itr)) + { + return itr; + } + ++itr; + } + return end; + } + + template + inline InputIterator contains_none(const InputIterator begin, const InputIterator end) const + { + InputIterator itr = begin; + while (end != itr) + { + if (contains(*itr)) + { + return itr; + } + ++itr; + } + return end; + } + + inline virtual unsigned long long int size() const + { + return table_size_; + } + + inline std::size_t element_count() const + { + return inserted_element_count_; + } + + inline double effective_fpp() const + { + /* + Note: + The effective false positive probability is calculated using the + designated table size and hash function count in conjunction with + the current number of inserted elements - not the user defined + predicated/expected number of inserted elements. + */ + return std::pow(1.0 - std::exp(-1.0 * salt_.size() * inserted_element_count_ / size()), 1.0 * salt_.size()); + } + + inline bloom_filter& operator &= (const bloom_filter& f) + { + /* intersection */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] &= f.bit_table_[i]; + } + } + return *this; + } + + inline bloom_filter& operator |= (const bloom_filter& f) + { + /* union */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] |= f.bit_table_[i]; + } + } + return *this; + } + + inline bloom_filter& operator ^= (const bloom_filter& f) + { + /* difference */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] ^= f.bit_table_[i]; + } + } + return *this; + } + + inline const cell_type* table() const + { + return bit_table_.data(); + } + + inline std::size_t hash_count() + { + return salt_.size(); + } + +protected: + + inline virtual void compute_indices(const bloom_type& hash, std::size_t& bit_index, std::size_t& bit) const + { + bit_index = hash % table_size_; + bit = bit_index % bits_per_char; + } + + void generate_unique_salt() + { + /* + Note: + A distinct hash function need not be implementation-wise + distinct. In the current implementation "seeding" a common + hash function with different values seems to be adequate. + */ + const unsigned int predef_salt_count = 128; + static const bloom_type predef_salt[predef_salt_count] = + { + 0xAAAAAAAA, 0x55555555, 0x33333333, 0xCCCCCCCC, + 0x66666666, 0x99999999, 0xB5B5B5B5, 0x4B4B4B4B, + 0xAA55AA55, 0x55335533, 0x33CC33CC, 0xCC66CC66, + 0x66996699, 0x99B599B5, 0xB54BB54B, 0x4BAA4BAA, + 0xAA33AA33, 0x55CC55CC, 0x33663366, 0xCC99CC99, + 0x66B566B5, 0x994B994B, 0xB5AAB5AA, 0xAAAAAA33, + 0x555555CC, 0x33333366, 0xCCCCCC99, 0x666666B5, + 0x9999994B, 0xB5B5B5AA, 0xFFFFFFFF, 0xFFFF0000, + 0xB823D5EB, 0xC1191CDF, 0xF623AEB3, 0xDB58499F, + 0xC8D42E70, 0xB173F616, 0xA91A5967, 0xDA427D63, + 0xB1E8A2EA, 0xF6C0D155, 0x4909FEA3, 0xA68CC6A7, + 0xC395E782, 0xA26057EB, 0x0CD5DA28, 0x467C5492, + 0xF15E6982, 0x61C6FAD3, 0x9615E352, 0x6E9E355A, + 0x689B563E, 0x0C9831A8, 0x6753C18B, 0xA622689B, + 0x8CA63C47, 0x42CC2884, 0x8E89919B, 0x6EDBD7D3, + 0x15B6796C, 0x1D6FDFE4, 0x63FF9092, 0xE7401432, + 0xEFFE9412, 0xAEAEDF79, 0x9F245A31, 0x83C136FC, + 0xC3DA4A8C, 0xA5112C8C, 0x5271F491, 0x9A948DAB, + 0xCEE59A8D, 0xB5F525AB, 0x59D13217, 0x24E7C331, + 0x697C2103, 0x84B0A460, 0x86156DA9, 0xAEF2AC68, + 0x23243DA5, 0x3F649643, 0x5FA495A8, 0x67710DF8, + 0x9A6C499E, 0xDCFB0227, 0x46A43433, 0x1832B07A, + 0xC46AFF3C, 0xB9C8FFF0, 0xC9500467, 0x34431BDF, + 0xB652432B, 0xE367F12B, 0x427F4C1B, 0x224C006E, + 0x2E7E5A89, 0x96F99AA5, 0x0BEB452A, 0x2FD87C39, + 0x74B2E1FB, 0x222EFD24, 0xF357F60C, 0x440FCB1E, + 0x8BBE030F, 0x6704DC29, 0x1144D12F, 0x948B1355, + 0x6D8FD7E9, 0x1C11A014, 0xADD1592F, 0xFB3C712E, + 0xFC77642F, 0xF9C4CE8C, 0x31312FB9, 0x08B0DD79, + 0x318FA6E7, 0xC040D23D, 0xC0589AA7, 0x0CA5C075, + 0xF874B172, 0x0CF914D5, 0x784D3280, 0x4E8CFEBC, + 0xC569F575, 0xCDB2A091, 0x2CC016B4, 0x5C5F4421 + }; + + if (salt_count_ <= predef_salt_count) + { + std::copy(predef_salt, + predef_salt + salt_count_, + std::back_inserter(salt_)); + for (unsigned int i = 0; i < salt_.size(); ++i) + { + /* + Note: + This is done to integrate the user defined random seed, + so as to allow for the generation of unique bloom filter + instances. + */ + salt_[i] = salt_[i] * salt_[(i + 3) % salt_.size()] + static_cast(random_seed_); + } + } + else + { + std::copy(predef_salt,predef_salt + predef_salt_count,std::back_inserter(salt_)); + srand(static_cast(random_seed_)); + while (salt_.size() < salt_count_) + { + bloom_type current_salt = static_cast(rand()) * static_cast(rand()); + if (0 == current_salt) continue; + if (salt_.end() == std::find(salt_.begin(), salt_.end(), current_salt)) + { + salt_.push_back(current_salt); + } + } + } + } + + inline bloom_type hash_ap(const unsigned char* begin, std::size_t remaining_length, bloom_type hash) const + { + const unsigned char* itr = begin; + unsigned int loop = 0; + while (remaining_length >= 8) + { + const unsigned int& i1 = *(reinterpret_cast(itr)); itr += sizeof(unsigned int); + const unsigned int& i2 = *(reinterpret_cast(itr)); itr += sizeof(unsigned int); + hash ^= (hash << 7) ^ i1 * (hash >> 3) ^ + (~((hash << 11) + (i2 ^ (hash >> 5)))); + remaining_length -= 8; + } + if (remaining_length) + { + if (remaining_length >= 4) + { + const unsigned int& i = *(reinterpret_cast(itr)); + if (loop & 0x01) + hash ^= (hash << 7) ^ i * (hash >> 3); + else + hash ^= (~((hash << 11) + (i ^ (hash >> 5)))); + ++loop; + remaining_length -= 4; + itr += sizeof(unsigned int); + } + if (remaining_length >= 2) + { + const unsigned short& i = *(reinterpret_cast(itr)); + if (loop & 0x01) + hash ^= (hash << 7) ^ i * (hash >> 3); + else + hash ^= (~((hash << 11) + (i ^ (hash >> 5)))); + ++loop; + remaining_length -= 2; + itr += sizeof(unsigned short); + } + if (remaining_length) + { + hash += ((*itr) ^ (hash * 0xA5A5A5A5)) + loop; + } + } + return hash; + } + +public: + std::vector salt_; + std::vector bit_table_; + unsigned int salt_count_; + unsigned long long int table_size_; + unsigned long long int raw_table_size_; + unsigned long long int projected_element_count_; + unsigned int inserted_element_count_; + unsigned long long int random_seed_; + double desired_false_positive_probability_; +}; + +inline bloom_filter operator & (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result &= b; + return result; +} + +inline bloom_filter operator | (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result |= b; + return result; +} + +inline bloom_filter operator ^ (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result ^= b; + return result; +} + + +} // namespace fc + + +FC_REFLECT( fc::bloom_filter, (salt_)(bit_table_)(salt_count_)(table_size_)(raw_table_size_)(projected_element_count_)(inserted_element_count_)(random_seed_)(desired_false_positive_probability_) ) +FC_REFLECT( fc::bloom_parameters::optimal_parameters_t, (number_of_hashes)(table_size) ) +FC_REFLECT( fc::bloom_parameters, (minimum_size)(maximum_size)(minimum_number_of_hashes)(maximum_number_of_hashes)(projected_element_count)(false_positive_probability)(random_seed)(optimal_parameters) ) + +/* + Note 1: + If it can be guaranteed that bits_per_char will be of the form 2^n then + the following optimization can be used: + + hash_table[bit_index >> n] |= bit_mask[bit_index & (bits_per_char - 1)]; + + Note 2: + For performance reasons where possible when allocating memory it should + be aligned (aligned_alloc) according to the architecture being used. +*/ From 135949ee19fb3cf8db7e7c41b9416764e2321881 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Fri, 15 May 2015 15:58:47 -0400 Subject: [PATCH 43/54] large integers are converted to strings in JSON printing --- src/io/json.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/io/json.cpp b/src/io/json.cpp index 2a116b8..a11cd35 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -589,11 +589,23 @@ namespace fc os << "null"; return; case variant::int64_type: - os << v.as_int64(); + { + int64_t i = v.as_int64(); + if( i >> 32 ) + os << v.as_string(); + else + os << i; return; + } case variant::uint64_type: - os << v.as_uint64(); + { + uint64_t i = v.as_uint64(); + if( i >> 32 ) + os << v.as_string(); + else + os << i; return; + } case variant::double_type: os << v.as_double(); return; From a3c284408ebe4f58bc6b2e858c0a796841747579 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Mon, 18 May 2015 13:40:01 -0400 Subject: [PATCH 44/54] make CLI prompt configurable --- include/fc/rpc/cli.hpp | 13 ++++++++----- src/rpc/cli.cpp | 6 +----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/fc/rpc/cli.hpp b/include/fc/rpc/cli.hpp index f4bb7a5..d6d9d11 100644 --- a/include/fc/rpc/cli.hpp +++ b/include/fc/rpc/cli.hpp @@ -53,16 +53,18 @@ namespace fc { namespace rpc { virtual void getline( const fc::string& prompt, fc::string& line ); + void set_prompt( const string& prompt ) { _prompt = prompt; } + private: void run() { - while( !_run_complete.canceled() ) - { - try { + while( !_run_complete.canceled() ) + { + try { std::string line; try { - getline( ">>> ", line ); + getline( _prompt.c_str(), line ); } catch ( const fc::eof_exception& e ) { @@ -88,8 +90,9 @@ namespace fc { namespace rpc { { std::cout << e.to_detail_string() << "\n"; } - } + } } + std::string _prompt = ">>>"; std::map > _result_formatters; fc::future _run_complete; }; diff --git a/src/rpc/cli.cpp b/src/rpc/cli.cpp index fd441e4..1cc04ed 100644 --- a/src/rpc/cli.cpp +++ b/src/rpc/cli.cpp @@ -1,4 +1,3 @@ - #include #include @@ -31,10 +30,7 @@ namespace fc { namespace rpc { -void cli::getline( - const fc::string& prompt, - fc::string& line - ) +void cli::getline( const fc::string& prompt, fc::string& line) { // getting file descriptor for C++ streams is near impossible // so we just assume it's the same as the C stream... From 71cca4559cf8a11de714206d1146c2e95b26aa9f Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Mon, 18 May 2015 15:05:25 -0400 Subject: [PATCH 45/54] adding quotes around large integers --- src/io/json.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/io/json.cpp b/src/io/json.cpp index a11cd35..2890ba3 100644 --- a/src/io/json.cpp +++ b/src/io/json.cpp @@ -591,8 +591,8 @@ namespace fc case variant::int64_type: { int64_t i = v.as_int64(); - if( i >> 32 ) - os << v.as_string(); + if( i > 0xffffffff ) + os << '"'<> 32 ) - os << v.as_string(); + if( i > 0xffffffff ) + os << '"'< Date: Tue, 19 May 2015 10:02:37 -0400 Subject: [PATCH 46/54] Add overloads of comparison operators to allow comparison of safe/uint128 and native integer classes --- CMakeLists.txt | 5 +++++ include/fc/safe.hpp | 48 ++++++++++++++++++++++++++++++++++++++++++ include/fc/uint128.hpp | 8 ++++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e75f58..7761879 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,6 +258,11 @@ target_include_directories(fc #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library}) target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries}) +if(MSVC) + set_source_files_properties( src/network/http/websocket.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) +endif(MSVC) + + IF(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY MATCHES "\\.(a|lib)$") IF(WIN32) add_definitions(/DBOOST_TEST_DYN_LINK) diff --git a/include/fc/safe.hpp b/include/fc/safe.hpp index 0c1a12e..7d876c0 100644 --- a/include/fc/safe.hpp +++ b/include/fc/safe.hpp @@ -64,26 +64,74 @@ namespace fc { { return a.value == b.value; } + friend bool operator == ( const safe& a, const T& b ) + { + return a.value == b; + } + friend bool operator == ( const T& a, const safe& b ) + { + return a == b.value; + } friend bool operator != ( const safe& a, const safe& b ) { return a.value != b.value; } + friend bool operator != ( const safe& a, const T& b ) + { + return a.value != b; + } + friend bool operator != ( const T& a, const safe& b ) + { + return a != b.value; + } friend bool operator < ( const safe& a, const safe& b ) { return a.value < b.value; } + friend bool operator < ( const safe& a, const T& b ) + { + return a.value < b; + } + friend bool operator < ( const T& a, const safe& b ) + { + return a < b.value; + } friend bool operator > ( const safe& a, const safe& b ) { return a.value > b.value; } + friend bool operator > ( const safe& a, const T& b ) + { + return a.value > b; + } + friend bool operator > ( const T& a, const safe& b ) + { + return a > b.value; + } friend bool operator >= ( const safe& a, const safe& b ) { return a.value >= b.value; } + friend bool operator >= ( const safe& a, const T& b ) + { + return a.value >= b; + } + friend bool operator >= ( const T& a, const safe& b ) + { + return a >= b.value; + } friend bool operator <= ( const safe& a, const safe& b ) { return a.value <= b.value; } + friend bool operator <= ( const safe& a, const T& b ) + { + return a.value <= b; + } + friend bool operator <= ( const T& a, const safe& b ) + { + return a <= b.value; + } T value = 0; }; diff --git a/include/fc/uint128.hpp b/include/fc/uint128.hpp index 41e6925..2e79a56 100644 --- a/include/fc/uint128.hpp +++ b/include/fc/uint128.hpp @@ -39,6 +39,7 @@ namespace fc bool operator == ( const uint128& o )const{ return hi == o.hi && lo == o.lo; } bool operator != ( const uint128& o )const{ return hi != o.hi || lo != o.lo; } bool operator < ( const uint128& o )const { return (hi == o.hi) ? lo < o.lo : hi < o.hi; } + bool operator < ( const int64_t& o )const { return *this < uint128(o); } bool operator !()const { return !(hi !=0 || lo != 0); } uint128 operator -()const { return ++uint128( ~hi, ~lo ); } uint128 operator ~()const { return uint128( ~hi, ~lo ); } @@ -72,10 +73,15 @@ namespace fc friend uint128 operator << ( const uint128& l, const uint128& r ) { return uint128(l)<<=r; } friend uint128 operator >> ( const uint128& l, const uint128& r ) { return uint128(l)>>=r; } friend bool operator > ( const uint128& l, const uint128& r ) { return r < l; } - + friend bool operator > ( const uint128& l, const int64_t& r ) { return uint128(r) < l; } + friend bool operator > ( const int64_t& l, const uint128& r ) { return r < uint128(l); } friend bool operator >= ( const uint128& l, const uint128& r ) { return l == r || l > r; } + friend bool operator >= ( const uint128& l, const int64_t& r ) { return l >= uint128(r); } + friend bool operator >= ( const int64_t& l, const uint128& r ) { return uint128(l) >= r; } friend bool operator <= ( const uint128& l, const uint128& r ) { return l == r || l < r; } + friend bool operator <= ( const uint128& l, const int64_t& r ) { return l <= uint128(r); } + friend bool operator <= ( const int64_t& l, const uint128& r ) { return uint128(l) <= r; } friend std::size_t hash_value( const uint128& v ) { return city_hash_size_t((const char*)&v, sizeof(v)); } From 2cbb00426c8562bc627369c93306b0153bcb9611 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Tue, 19 May 2015 11:40:47 -0400 Subject: [PATCH 47/54] adding ability to get the count of items in a static variant --- include/fc/static_variant.hpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/include/fc/static_variant.hpp b/include/fc/static_variant.hpp index 46a96ca..3db2bdf 100644 --- a/include/fc/static_variant.hpp +++ b/include/fc/static_variant.hpp @@ -155,6 +155,7 @@ struct type_info { static const bool no_reference_types = false; static const bool no_duplicates = position::pos == -1 && type_info::no_duplicates; static const size_t size = type_info::size > sizeof(T&) ? type_info::size : sizeof(T&); + static const size_t count = 1 + type_info::count; }; template @@ -162,12 +163,14 @@ struct type_info { static const bool no_reference_types = type_info::no_reference_types; static const bool no_duplicates = position::pos == -1 && type_info::no_duplicates; static const size_t size = type_info::size > sizeof(T) ? type_info::size : sizeof(T&); + static const size_t count = 1 + type_info::count; }; template<> struct type_info<> { static const bool no_reference_types = true; static const bool no_duplicates = true; + static const size_t count = 0; static const size_t size = 0; }; @@ -314,13 +317,15 @@ public: return impl::storage_ops<0, Types...>::apply(_tag, storage, v); } - void set_which( int w ) { - this->~static_variant(); - _tag = w; - impl::storage_ops<0, Types...>::con(_tag, storage); - } + static int count() { return impl::type_info::count; } + void set_which( int w ) { + FC_ASSERT( w < count() ); + this->~static_variant(); + _tag = w; + impl::storage_ops<0, Types...>::con(_tag, storage); + } - int which() const {return _tag;} + int which() const {return _tag;} }; template @@ -369,4 +374,5 @@ struct visitor { s.visit( to_static_variant(ar[1]) ); } + template struct get_typename { static const char* name() { return typeid(static_variant).name(); } }; } // namespace fc From 043ead5579475e3605e3c5ef47d5e0ba9b4f807a Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 22 May 2015 16:15:00 -0400 Subject: [PATCH 48/54] Fix compile with microsoft c++ which still doesn't support noexcept --- src/crypto/_elliptic_impl_pub.hpp | 15 ++++++++------- src/crypto/elliptic_impl_pub.cpp | 15 ++++++++------- src/crypto/elliptic_openssl.cpp | 14 +++++++------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/crypto/_elliptic_impl_pub.hpp b/src/crypto/_elliptic_impl_pub.hpp index aa547be..b982ba1 100644 --- a/src/crypto/_elliptic_impl_pub.hpp +++ b/src/crypto/_elliptic_impl_pub.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include /* public_key_impl implementation based on openssl * used by mixed + openssl @@ -12,21 +13,21 @@ void _init_lib(); class public_key_impl { public: - public_key_impl() noexcept; - public_key_impl( const public_key_impl& cpy ) noexcept; - public_key_impl( public_key_impl&& cpy ) noexcept; - ~public_key_impl() noexcept; + public_key_impl() BOOST_NOEXCEPT; + public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT; + public_key_impl( public_key_impl&& cpy ) BOOST_NOEXCEPT; + ~public_key_impl() BOOST_NOEXCEPT; - public_key_impl& operator=( const public_key_impl& pk ) noexcept; + public_key_impl& operator=( const public_key_impl& pk ) BOOST_NOEXCEPT; - public_key_impl& operator=( public_key_impl&& pk ) noexcept; + public_key_impl& operator=( public_key_impl&& pk ) BOOST_NOEXCEPT; static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check); EC_KEY* _key = nullptr; private: - void free_key() noexcept; + void free_key() BOOST_NOEXCEPT; }; }}} diff --git a/src/crypto/elliptic_impl_pub.cpp b/src/crypto/elliptic_impl_pub.cpp index d3f0724..c9bbb08 100644 --- a/src/crypto/elliptic_impl_pub.cpp +++ b/src/crypto/elliptic_impl_pub.cpp @@ -1,4 +1,5 @@ #include +#include #include "_elliptic_impl_pub.hpp" @@ -7,29 +8,29 @@ namespace fc { namespace ecc { namespace detail { - public_key_impl::public_key_impl() noexcept + public_key_impl::public_key_impl() BOOST_NOEXCEPT { _init_lib(); } - public_key_impl::public_key_impl( const public_key_impl& cpy ) noexcept + public_key_impl::public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT { _init_lib(); *this = cpy; } - public_key_impl::public_key_impl( public_key_impl&& cpy ) noexcept + public_key_impl::public_key_impl( public_key_impl&& cpy ) BOOST_NOEXCEPT { _init_lib(); *this = cpy; } - public_key_impl::~public_key_impl() noexcept + public_key_impl::~public_key_impl() BOOST_NOEXCEPT { free_key(); } - public_key_impl& public_key_impl::operator=( const public_key_impl& pk ) noexcept + public_key_impl& public_key_impl::operator=( const public_key_impl& pk ) BOOST_NOEXCEPT { if (pk._key == nullptr) { @@ -42,7 +43,7 @@ namespace fc { namespace ecc { return *this; } - public_key_impl& public_key_impl::operator=( public_key_impl&& pk ) noexcept + public_key_impl& public_key_impl::operator=( public_key_impl&& pk ) BOOST_NOEXCEPT { if ( this != &pk ) { free_key(); @@ -52,7 +53,7 @@ namespace fc { namespace ecc { return *this; } - void public_key_impl::free_key() noexcept + void public_key_impl::free_key() BOOST_NOEXCEPT { if( _key != nullptr ) { diff --git a/src/crypto/elliptic_openssl.cpp b/src/crypto/elliptic_openssl.cpp index 2d22056..78170a7 100644 --- a/src/crypto/elliptic_openssl.cpp +++ b/src/crypto/elliptic_openssl.cpp @@ -21,29 +21,29 @@ namespace fc { namespace ecc { class private_key_impl { public: - private_key_impl() noexcept + private_key_impl() BOOST_NOEXCEPT { _init_lib(); } - private_key_impl( const private_key_impl& cpy ) noexcept + private_key_impl( const private_key_impl& cpy ) BOOST_NOEXCEPT { _init_lib(); *this = cpy; } - private_key_impl( private_key_impl&& cpy ) noexcept + private_key_impl( private_key_impl&& cpy ) BOOST_NOEXCEPT { _init_lib(); *this = cpy; } - ~private_key_impl() noexcept + ~private_key_impl() BOOST_NOEXCEPT { free_key(); } - private_key_impl& operator=( const private_key_impl& pk ) noexcept + private_key_impl& operator=( const private_key_impl& pk ) BOOST_NOEXCEPT { if (pk._key == nullptr) { @@ -56,7 +56,7 @@ namespace fc { namespace ecc { return *this; } - private_key_impl& operator=( private_key_impl&& pk ) noexcept + private_key_impl& operator=( private_key_impl&& pk ) BOOST_NOEXCEPT { if ( this != &pk ) { free_key(); @@ -69,7 +69,7 @@ namespace fc { namespace ecc { EC_KEY* _key = nullptr; private: - void free_key() noexcept + void free_key() BOOST_NOEXCEPT { if( _key != nullptr ) { From 637f475e4429d225d05ca49b7d21f1ab64e859bf Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Wed, 27 May 2015 11:26:04 -0400 Subject: [PATCH 49/54] parse doubles as strings --- CMakeLists.txt | 3 ++- include/fc/exception/exception.hpp | 10 +++++++++- include/fc/io/json_relaxed.hpp | 16 ++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c377e9..eb4eae6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ SET(BOOST_COMPONENTS) LIST(APPEND BOOST_COMPONENTS thread date_time system filesystem program_options signals serialization chrono unit_test_framework context locale iostreams) SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" ) + IF( ECC_IMPL STREQUAL openssl ) SET( ECC_REST src/crypto/elliptic_impl_pub.cpp ) ELSE( ECC_IMPL STREQUAL openssl ) @@ -270,7 +271,7 @@ target_include_directories(fc ) #target_link_libraries( fc PUBLIC easylzma_static scrypt udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${ECC_LIB} ) -target_link_libraries( fc PUBLIC easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries} ${ECC_LIB} ) +target_link_libraries( fc PUBLIC -L/usr/local/lib easylzma_static udt ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${rt_library} ${readline_libraries} ${ECC_LIB} ) if(MSVC) set_source_files_properties( src/network/http/websocket.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) diff --git a/include/fc/exception/exception.hpp b/include/fc/exception/exception.hpp index baffcdf..5de13f9 100644 --- a/include/fc/exception/exception.hpp +++ b/include/fc/exception/exception.hpp @@ -294,6 +294,14 @@ namespace fc } // namespace fc +#if __APPLE__ + #define LIKELY(x) __builtin_expect((long)!!(x), 1L) + #define UNLIKELY(x) __builtin_expect((long)!!(x), 0L) +#else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) +#endif + /** *@brief: Workaround for varying preprocessing behavior between MSVC and gcc */ @@ -304,7 +312,7 @@ namespace fc #define FC_ASSERT( TEST, ... ) \ FC_EXPAND_MACRO( \ FC_MULTILINE_MACRO_BEGIN \ - if( !(TEST) ) \ + if( UNLIKELY(!(TEST)) ) \ FC_THROW_EXCEPTION( fc::assert_exception, #TEST ": " __VA_ARGS__ ); \ FC_MULTILINE_MACRO_END \ ) diff --git a/include/fc/io/json_relaxed.hpp b/include/fc/io/json_relaxed.hpp index aa50948..202fc54 100644 --- a/include/fc/io/json_relaxed.hpp +++ b/include/fc/io/json_relaxed.hpp @@ -343,8 +343,8 @@ namespace fc { namespace json_relaxed template fc::variant parseNumberOrStr( const fc::string& token ) - { - + { try { + //ilog( (token) ); size_t i = 0, n = token.length(); if( n == 0 ) FC_THROW_EXCEPTION( parse_error_exception, "expected: non-empty token, got: empty token" ); @@ -426,12 +426,14 @@ namespace fc { namespace json_relaxed if( i >= n ) return parseInt<10>( token, start ); char c = token[i++]; + //idump((c)(std::string()+c)); switch( c ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case '.': + return fc::variant(token); if( dot_ok ) { dot_ok = false; @@ -442,7 +444,9 @@ namespace fc { namespace json_relaxed return fc::variant( fc::to_double(token.c_str()) ); } + //idump((i)); c = token[i+1]; + //idump((c)); switch( c ) { case '0': case '1': case '2': case '3': case '4': @@ -466,7 +470,7 @@ namespace fc { namespace json_relaxed FC_THROW_EXCEPTION( parse_error_exception, "expected digit after '.'" ); return fc::variant( token ); default: - FC_THROW_EXCEPTION( parse_error_exception, "illegal character '{c}' in token", ( "c", c ) ); + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '{c}' in token", ( "c", c )("i",int(c)) ); } } else @@ -554,7 +558,7 @@ namespace fc { namespace json_relaxed FC_THROW_EXCEPTION( parse_error_exception, "illegal character '{c}' in number", ( "c", c ) ); } } - } + } FC_CAPTURE_AND_RETHROW( (token) ) } template variant_object objectFromStream( T& in ) @@ -641,13 +645,13 @@ namespace fc { namespace json_relaxed template variant numberFromStream( T& in ) - { + { try { fc::string token = tokenFromStream(in); variant result = parseNumberOrStr( token ); if( strict && !(result.is_int64() || result.is_uint64() || result.is_double()) ) FC_THROW_EXCEPTION( parse_error_exception, "expected: number" ); return result; - } + } FC_CAPTURE_AND_RETHROW() } template variant wordFromStream( T& in ) From 8012ab47051cf67f30096d06c74fae3b76e00902 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 27 May 2015 14:54:11 -0400 Subject: [PATCH 50/54] Fix crash on exit with websocket_client --- src/network/http/websocket.cpp | 63 +++++++++++++++++----------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 2bf7a60..567c7d6 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -54,7 +54,7 @@ namespace fc { namespace http { typedef websocketpp::transport::asio::endpoint transport_type; - + static const long timeout_open_handshake = 0; }; struct asio_tls_with_stub_log : public websocketpp::config::asio_tls { @@ -95,7 +95,7 @@ namespace fc { namespace http { typedef websocketpp::transport::asio::endpoint transport_type; - + static const long timeout_open_handshake = 0; }; struct asio_tls_stub_log : public websocketpp::config::asio_tls { @@ -216,7 +216,7 @@ namespace fc { namespace http { if( _connections.find(hdl) != _connections.end() ) { _connections[hdl]->closed(); - _connections.erase( hdl ); + _connections.erase( hdl ); } else { @@ -232,7 +232,7 @@ namespace fc { namespace http { if( _connections.find(hdl) != _connections.end() ) { _connections[hdl]->closed(); - _connections.erase( hdl ); + _connections.erase( hdl ); } else { @@ -317,7 +317,7 @@ namespace fc { namespace http { con->set_body( response ); con->set_status( websocketpp::http::status_code::ok ); } catch ( const fc::exception& e ) - { + { edump((e.to_detail_string())); } current_con->closed(); @@ -328,7 +328,7 @@ namespace fc { namespace http { _server.set_close_handler( [&]( connection_hdl hdl ){ _server_thread.async( [&](){ _connections[hdl]->closed(); - _connections.erase( hdl ); + _connections.erase( hdl ); }).wait(); }); @@ -339,7 +339,7 @@ namespace fc { namespace http { if( _connections.find(hdl) != _connections.end() ) { _connections[hdl]->closed(); - _connections.erase( hdl ); + _connections.erase( hdl ); } }).wait(); } @@ -375,13 +375,13 @@ namespace fc { namespace http { - typedef websocketpp::client websocket_client_type; - typedef websocketpp::client websocket_tls_client_type; + typedef websocketpp::client websocket_client_type; + typedef websocketpp::client websocket_tls_client_type; - typedef websocket_client_type::connection_ptr websocket_client_connection_type; - typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type; + typedef websocket_client_type::connection_ptr websocket_client_connection_type; + typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type; - class websocket_client_impl + class websocket_client_impl { public: typedef websocket_client_type::message_ptr message_ptr; @@ -393,12 +393,12 @@ namespace fc { namespace http { _client.set_message_handler( [&]( connection_hdl hdl, message_ptr msg ){ _client_thread.async( [&](){ wdump((msg->get_payload())); - _connection->on_message( msg->get_payload() ); + if( _connection ) + _connection->on_message( msg->get_payload() ); }).wait(); }); _client.set_close_handler( [=]( connection_hdl hdl ){ - if( _connection ) - _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); + _client_thread.async( [&](){ if( _connection ) {_connection->closed(); _connection.reset();} } ).wait(); if( _closed ) _closed->set_value(); }); _client.set_fail_handler( [=]( connection_hdl hdl ){ @@ -406,9 +406,9 @@ namespace fc { namespace http { auto message = con->get_ec().message(); if( _connection ) _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); - if( _connected && !_connected->ready() ) + if( _connected && !_connected->ready() ) _connected->set_exception( exception_ptr( new FC_EXCEPTION( exception, "${message}", ("message",message)) ) ); - if( _closed ) + if( _closed ) _closed->set_value(); }); @@ -419,6 +419,7 @@ namespace fc { namespace http { if(_connection ) { _connection->close(0, "client closed"); + _connection.reset(); _closed->wait(); } } @@ -431,7 +432,7 @@ namespace fc { namespace http { - class websocket_tls_client_impl + class websocket_tls_client_impl { public: typedef websocket_tls_client_type::message_ptr message_ptr; @@ -450,11 +451,11 @@ namespace fc { namespace http { if( _connection ) { try { - _client_thread.async( [&](){ - wlog(". ${p}", ("p",uint64_t(_connection.get()))); - if( !_shutting_down && !_closed && _connection ) - _connection->closed(); - _connection.reset(); + _client_thread.async( [&](){ + wlog(". ${p}", ("p",uint64_t(_connection.get()))); + if( !_shutting_down && !_closed && _connection ) + _connection->closed(); + _connection.reset(); } ).wait(); } catch ( const fc::exception& e ) { @@ -469,9 +470,9 @@ namespace fc { namespace http { auto message = con->get_ec().message(); if( _connection ) _client_thread.async( [&](){ if( _connection ) _connection->closed(); _connection.reset(); } ).wait(); - if( _connected && !_connected->ready() ) + if( _connected && !_connected->ready() ) _connected->set_exception( exception_ptr( new FC_EXCEPTION( exception, "${message}", ("message",message)) ) ); - if( _closed ) + if( _closed ) _closed->set_value(); }); @@ -529,8 +530,8 @@ namespace fc { namespace http { my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); } - void websocket_server::start_accept() { - my->_server.start_accept(); + void websocket_server::start_accept() { + my->_server.start_accept(); } @@ -553,8 +554,8 @@ namespace fc { namespace http { my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) ); } - void websocket_tls_server::start_accept() { - my->_server.start_accept(); + void websocket_tls_server::start_accept() { + my->_server.start_accept(); } @@ -568,7 +569,7 @@ namespace fc { namespace http { websocket_connection_ptr websocket_client::connect( const std::string& uri ) { try { - if( uri.substr(0,4) == "wss:" ) + if( uri.substr(0,4) == "wss:" ) return secure_connect(uri); FC_ASSERT( uri.substr(0,3) == "ws:" ); @@ -595,7 +596,7 @@ namespace fc { namespace http { websocket_connection_ptr websocket_client::secure_connect( const std::string& uri ) { try { - if( uri.substr(0,3) == "ws:" ) + if( uri.substr(0,3) == "ws:" ) return connect(uri); FC_ASSERT( uri.substr(0,4) == "wss:" ); // wlog( "connecting to ${uri}", ("uri",uri)); From 83d5bcb14743c36a973fe94e3dadec54fda763ff Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 27 May 2015 15:04:34 -0400 Subject: [PATCH 51/54] Mark websockets as logging to rpc --- src/network/http/websocket.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/network/http/websocket.cpp b/src/network/http/websocket.cpp index 567c7d6..9f99642 100644 --- a/src/network/http/websocket.cpp +++ b/src/network/http/websocket.cpp @@ -11,6 +11,11 @@ #include #include +#ifdef DEFAULT_LOGGER +# undef DEFAULT_LOGGER +#endif +#define DEFAULT_LOGGER "rpc" + namespace fc { namespace http { namespace detail { From d0b4b6492301b52d638b83614261be7e49061187 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 27 May 2015 16:15:49 -0400 Subject: [PATCH 52/54] Remove log spam --- include/fc/network/http/websocket.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/fc/network/http/websocket.hpp b/include/fc/network/http/websocket.hpp index 1969e09..c0c7266 100644 --- a/include/fc/network/http/websocket.hpp +++ b/include/fc/network/http/websocket.hpp @@ -17,7 +17,7 @@ namespace fc { namespace http { class websocket_connection { public: - virtual ~websocket_connection(){ wlog("."); }; + virtual ~websocket_connection(){} virtual void send_message( const std::string& message ) = 0; virtual void close( int64_t code, const std::string& reason ){}; void on_message( const std::string& message ) { _on_message(message); } @@ -31,7 +31,7 @@ namespace fc { namespace http { fc::signal closed; private: - fc::any _session_data; + fc::any _session_data; std::function _on_message; std::function _on_http; }; @@ -39,7 +39,7 @@ namespace fc { namespace http { typedef std::function on_connection_handler; - class websocket_server + class websocket_server { public: websocket_server(); @@ -56,10 +56,10 @@ namespace fc { namespace http { }; - class websocket_tls_server + class websocket_tls_server { public: - websocket_tls_server( const std::string& server_pem = std::string(), + websocket_tls_server( const std::string& server_pem = std::string(), const std::string& ssl_password = std::string()); ~websocket_tls_server(); From 994c4097fa457a8b3f92d7ee660d30e1ffa0789f Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Thu, 28 May 2015 08:43:43 -0400 Subject: [PATCH 53/54] fix build --- include/fc/rpc/api_connection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fc/rpc/api_connection.hpp b/include/fc/rpc/api_connection.hpp index c037c22..5316285 100644 --- a/include/fc/rpc/api_connection.hpp +++ b/include/fc/rpc/api_connection.hpp @@ -113,7 +113,7 @@ namespace fc { R call_generic( const std::function,Args...)>& f, variants::const_iterator a0, variants::const_iterator e ) { FC_ASSERT( a0 != e, "too few arguments passed to method" ); - detail::callback_functor arg0( *this, a0->as() ); + detail::callback_functor arg0( get_connection(), a0->as() ); return call_generic( this->bind_first_arg,Args...>( f, std::function(arg0) ), a0+1, e ); } template From af636c10a28b38c0c2418c2650a23fac2979b950 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 28 May 2015 10:22:47 -0400 Subject: [PATCH 54/54] Add data_size() member to hash classes --- include/fc/crypto/ripemd160.hpp | 1 + include/fc/crypto/sha1.hpp | 1 + include/fc/crypto/sha224.hpp | 1 + include/fc/crypto/sha256.hpp | 1 + include/fc/crypto/sha512.hpp | 1 + 5 files changed, 5 insertions(+) diff --git a/include/fc/crypto/ripemd160.hpp b/include/fc/crypto/ripemd160.hpp index 3200873..912c392 100644 --- a/include/fc/crypto/ripemd160.hpp +++ b/include/fc/crypto/ripemd160.hpp @@ -18,6 +18,7 @@ class ripemd160 explicit operator string()const; char* data()const; + size_t data_size()const { return 160/8; } static ripemd160 hash( const fc::sha512& h ); static ripemd160 hash( const fc::sha256& h ); diff --git a/include/fc/crypto/sha1.hpp b/include/fc/crypto/sha1.hpp index 62d077c..32fc2e4 100644 --- a/include/fc/crypto/sha1.hpp +++ b/include/fc/crypto/sha1.hpp @@ -14,6 +14,7 @@ class sha1 operator string()const; char* data()const; + size_t data_size()const { return 20; } static sha1 hash( const char* d, uint32_t dlen ); static sha1 hash( const string& ); diff --git a/include/fc/crypto/sha224.hpp b/include/fc/crypto/sha224.hpp index 7f3e908..66db6ea 100644 --- a/include/fc/crypto/sha224.hpp +++ b/include/fc/crypto/sha224.hpp @@ -17,6 +17,7 @@ class sha224 operator string()const; char* data()const; + size_t data_size()const { return 224 / 8; } static sha224 hash( const char* d, uint32_t dlen ); static sha224 hash( const string& ); diff --git a/include/fc/crypto/sha256.hpp b/include/fc/crypto/sha256.hpp index c40c83a..387a1d0 100644 --- a/include/fc/crypto/sha256.hpp +++ b/include/fc/crypto/sha256.hpp @@ -18,6 +18,7 @@ class sha256 operator string()const; char* data()const; + size_t data_size()const { return 256 / 8; } static sha256 hash( const char* d, uint32_t dlen ); static sha256 hash( const string& ); diff --git a/include/fc/crypto/sha512.hpp b/include/fc/crypto/sha512.hpp index 3fa2352..ef10887 100644 --- a/include/fc/crypto/sha512.hpp +++ b/include/fc/crypto/sha512.hpp @@ -15,6 +15,7 @@ class sha512 operator string()const; char* data()const; + size_t data_size()const { return 512 / 8; } static sha512 hash( const char* d, uint32_t dlen ); static sha512 hash( const string& );