diff --git a/include/fc/crypto/elliptic.hpp b/include/fc/crypto/elliptic.hpp index dd60da736..d39ddc0c9 100644 --- a/include/fc/crypto/elliptic.hpp +++ b/include/fc/crypto/elliptic.hpp @@ -28,6 +28,13 @@ namespace fc { typedef fc::sha256 blinded_hash; typedef fc::sha256 blind_signature; + enum canonical_signature_type + { + non_canonical, + bip_0062, + fc_canonical + }; + /** * @class public_key * @brief contains only the public point of an elliptic curve key. @@ -47,7 +54,7 @@ namespace fc { public_key( const public_key_data& v ); public_key( const public_key_point_data& v ); - public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical = true ); + public_key( const compact_signature& c, const fc::sha256& digest, canonical_signature_type canon_type = fc_canonical ); public_key child( const fc::sha256& offset )const; @@ -78,10 +85,12 @@ namespace fc { unsigned int fingerprint() const; + static bool is_canonical( const compact_signature& c, canonical_signature_type canon_type ); + private: 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; }; @@ -123,7 +132,7 @@ namespace fc { fc::sha512 get_shared_secret( const public_key& pub )const; // signature sign( const fc::sha256& digest )const; - compact_signature sign_compact( const fc::sha256& digest, bool require_canonical = true )const; + compact_signature sign_compact( const fc::sha256& digest, canonical_signature_type canon_type = fc_canonical )const; // bool verify( const fc::sha256& digest, const signature& sig ); public_key get_public_key()const; diff --git a/src/crypto/elliptic_common.cpp b/src/crypto/elliptic_common.cpp index 1377c2d3b..2dac1a2b6 100644 --- a/src/crypto/elliptic_common.cpp +++ b/src/crypto/elliptic_common.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #ifdef _WIN32 # include #else @@ -18,6 +21,8 @@ namespace fc { namespace ecc { + using namespace boost::multiprecision::literals; + namespace detail { typedef fc::array chr37; @@ -168,13 +173,49 @@ namespace fc { namespace ecc { return (fp[0] << 24) | (fp[1] << 16) | (fp[2] << 8) | fp[3]; } - 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)); + bool is_fc_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)); + } + + bool is_bip_0062_canonical( const compact_signature& c ) + { + constexpr boost::multiprecision::uint256_t n_2 = + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0_cppui256; + + boost::multiprecision::uint256_t sig = 0; + + // boost endian conversions are only supported for native c++ types, + // so we need to convert in 64 bit words + for( size_t i = 0; i < 4; i++ ) + { + sig <<= 64; + sig += boost::endian::big_to_native( *( uint64_t* )( c.data + 33 + ( i * 8 ) ) ); + } + + // BIP-0062 states that sig must be in [1,n/2], however because a sig of value 0 is an invalid + // signature under all circumstances, the lower bound does not need checking + return sig <= n_2; + } + + + bool public_key::is_canonical( const compact_signature& c, canonical_signature_type canon_type ) + { + switch( canon_type ) + { + case bip_0062: + return is_bip_0062_canonical( c ); + case fc_canonical: + return is_fc_canonical( c ); + case non_canonical: + return true; + } } + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) { ssl_bignum z; @@ -269,7 +310,7 @@ namespace fc { namespace ecc { memcpy( dest, key.begin(), key.size() ); return result; } - + extended_public_key extended_public_key::deserialize( const extended_key_data& data ) { return from_base58( _to_base58( data ) ); @@ -340,7 +381,7 @@ namespace fc { namespace ecc { memcpy( dest, key.data(), key.data_size() ); return result; } - + extended_private_key extended_private_key::deserialize( const extended_key_data& data ) { return from_base58( _to_base58( data ) ); diff --git a/src/crypto/elliptic_impl_priv.cpp b/src/crypto/elliptic_impl_priv.cpp index 585ffdef1..c60b92f57 100644 --- a/src/crypto/elliptic_impl_priv.cpp +++ b/src/crypto/elliptic_impl_priv.cpp @@ -85,7 +85,7 @@ namespace fc { namespace ecc { return secp256k1_nonce_function_default( nonce32, msg32, key32, *extra, nullptr ); } - compact_signature private_key::sign_compact( const fc::sha256& digest, bool require_canonical )const + compact_signature private_key::sign_compact( const fc::sha256& digest, canonical_signature_type canon_type )const { FC_ASSERT( my->_key != empty_priv ); compact_signature result; @@ -94,7 +94,7 @@ namespace fc { namespace ecc { do { 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( require_canonical && !public_key::is_canonical( result ) ); + } while( !public_key::is_canonical( result, canon_type ) ); result.begin()[0] = 27 + 4 + recid; return result; } diff --git a/src/crypto/elliptic_secp256k1.cpp b/src/crypto/elliptic_secp256k1.cpp index 3a33d1e3c..cd24210d3 100644 --- a/src/crypto/elliptic_secp256k1.cpp +++ b/src/crypto/elliptic_secp256k1.cpp @@ -152,16 +152,13 @@ namespace fc { namespace ecc { my->_key = dat; } - public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + public_key::public_key( const compact_signature& c, const fc::sha256& digest, canonical_signature_type canon_type ) { 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" ); - } + FC_ASSERT( is_canonical( c, canon_type ), "signature is not canonical" ); unsigned int pk_len; 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 ) ); @@ -487,24 +484,24 @@ namespace fc { namespace ecc { return secp256k1_rangeproof_verify( detail::_get_context(), &min_val, &max_val, (const unsigned char*)&commit, (const unsigned char*)proof.data(), proof.size() ); } - std::vector range_proof_sign( uint64_t min_value, - const commitment_type& commit, - const blind_factor_type& commit_blind, + std::vector range_proof_sign( uint64_t min_value, + const commitment_type& commit, + const blind_factor_type& commit_blind, const blind_factor_type& nonce, int8_t base10_exp, uint8_t min_bits, uint64_t actual_value ) { - int proof_len = 5134; + int proof_len = 5134; std::vector proof(proof_len); - FC_ASSERT( secp256k1_rangeproof_sign( detail::_get_context(), - (unsigned char*)proof.data(), - &proof_len, min_value, - (const unsigned char*)&commit, - (const unsigned char*)&commit_blind, - (const unsigned char*)&nonce, + FC_ASSERT( secp256k1_rangeproof_sign( detail::_get_context(), + (unsigned char*)proof.data(), + &proof_len, min_value, + (const unsigned char*)&commit, + (const unsigned char*)&commit_blind, + (const unsigned char*)&nonce, base10_exp, min_bits, actual_value ) ); proof.resize(proof_len); return proof; @@ -513,16 +510,16 @@ namespace fc { namespace ecc { bool verify_range_proof_rewind( blind_factor_type& blind_out, uint64_t& value_out, - string& message_out, + string& message_out, const blind_factor_type& nonce, - uint64_t& min_val, - uint64_t& max_val, - commitment_type commit, + uint64_t& min_val, + uint64_t& max_val, + commitment_type commit, const std::vector& proof ) { char msg[4096]; int mlen = 0; - FC_ASSERT( secp256k1_rangeproof_rewind( detail::_get_context(), + FC_ASSERT( secp256k1_rangeproof_rewind( detail::_get_context(), (unsigned char*)&blind_out, &value_out, (unsigned char*)msg, @@ -541,12 +538,12 @@ namespace fc { namespace ecc { range_proof_info range_get_info( const std::vector& proof ) { range_proof_info result; - FC_ASSERT( secp256k1_rangeproof_info( detail::_get_context(), - (int*)&result.exp, - (int*)&result.mantissa, - (uint64_t*)&result.min_value, - (uint64_t*)&result.max_value, - (const unsigned char*)proof.data(), + FC_ASSERT( secp256k1_rangeproof_info( detail::_get_context(), + (int*)&result.exp, + (int*)&result.mantissa, + (uint64_t*)&result.min_value, + (uint64_t*)&result.max_value, + (const unsigned char*)proof.data(), (int)proof.size() ) ); return result; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d44618e1b..614c6281f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,6 +35,9 @@ target_link_libraries( ecc_test fc ) add_executable( log_test crypto/log_test.cpp ) target_link_libraries( log_test fc ) +add_executable( ecdsa_canon_test crypto/ecdsa_canon_test.cpp ) +target_link_libraries( ecdsa_canon_test fc ) + #add_executable( test_aes aes_test.cpp ) #target_link_libraries( test_aes fc ${rt_library} ${pthread_library} ) #add_executable( test_sleep sleep.cpp ) @@ -55,6 +58,7 @@ add_executable( all_tests all_tests.cpp crypto/dh_test.cpp crypto/rand_test.cpp crypto/sha_tests.cpp + crypto/ecdsa_canon_test.cpp network/ntp_test.cpp network/http/websocket_test.cpp thread/task_cancel.cpp diff --git a/tests/crypto/ecdsa_canon_test.cpp b/tests/crypto/ecdsa_canon_test.cpp new file mode 100644 index 000000000..8967a7ddf --- /dev/null +++ b/tests/crypto/ecdsa_canon_test.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include + +uint8_t fc_canon[65] = { + /*rec id */ 0x20, + /* r */ 0x12, 0x65, 0xbb, 0xa6, 0xde, 0xb1, 0xba, 0xf0, 0x79, 0x3b, 0xc5, 0x08, 0x77, 0x99, 0x27, 0x2b, 0x5d, 0x2e, 0xf6, 0xff, 0x9d, 0x72, 0x21, 0x8a, 0x68, 0x82, 0x25, 0x9d, 0x98, 0x94, 0xda, 0xd7, + /* s */ 0x66, 0x36, 0x6a, 0x28, 0xfa, 0x4d, 0xb9, 0x06, 0x66, 0x1e, 0x3b, 0xf2, 0x68, 0x2d, 0x27, 0x9b, 0xeb, 0x9d, 0x2f, 0x4c, 0xc4, 0x36, 0xee, 0xbf, 0xa3, 0x52, 0xe8, 0x4f, 0xc5, 0xd0, 0x47, 0xee +}; + +uint8_t bip_0062_canon[65] = { + /*rec id */ 0x20, + /* r */ 0xe3, 0x9a, 0xff, 0x4c, 0x7a, 0xea, 0x6f, 0xe8, 0x50, 0xad, 0x9f, 0x45, 0x4a, 0xdc, 0x59, 0x61, 0x15, 0xa8, 0xfb, 0x85, 0xd7, 0xb6, 0xaa, 0x2d, 0x2a, 0x31, 0xbe, 0x84, 0x05, 0x85, 0x93, 0x5f, + /* s */ 0x56, 0x6d, 0x35, 0x9d, 0x1f, 0x57, 0x6a, 0x59, 0xb5, 0x0c, 0x4e, 0x31, 0x45, 0x04, 0x24, 0x43, 0x5a, 0x37, 0x1c, 0x35, 0x02, 0x2a, 0xab, 0x20, 0x1f, 0xcf, 0x55, 0x1b, 0xee, 0x58, 0xdd, 0x10 +}; + +uint8_t bip_0062_canon2[65] = { + /*rec id */ 0x20, + /* r */ 0x12, 0x65, 0xbb, 0xa6, 0xde, 0xb1, 0xba, 0xf0, 0x79, 0x3b, 0xc5, 0x08, 0x77, 0x99, 0x27, 0x2b, 0x5d, 0x2e, 0xf6, 0xff, 0x9d, 0x72, 0x21, 0x8a, 0x68, 0x82, 0x25, 0x9d, 0x98, 0x94, 0xda, 0xd7, + /* s */ 0x2f, 0x11, 0x18, 0xe5, 0xca, 0xce, 0xea, 0xce, 0x60, 0x2c, 0x8d, 0x97, 0xcb, 0x15, 0x87, 0xc1, 0x06, 0xa1, 0xe6, 0x94, 0x05, 0xe8, 0x62, 0x1e, 0xae, 0x50, 0x3c, 0xe4, 0x04, 0xad, 0x8f, 0x29 +}; + +// -s (mod n) +uint8_t non_canon[65] = { + /*rec id */ 0x20, + /* r */ 0x12, 0x65, 0xbb, 0xa6, 0xde, 0xb1, 0xba, 0xf0, 0x79, 0x3b, 0xc5, 0x08, 0x77, 0x99, 0x27, 0x2b, 0x5d, 0x2e, 0xf6, 0xff, 0x9d, 0x72, 0x21, 0x8a, 0x68, 0x82, 0x25, 0x9d, 0x98, 0x94, 0xda, 0xd7, + /* s */ 0xa9, 0x92, 0xca, 0x62, 0xe0, 0xa8, 0x95, 0xa6, 0x4a, 0xf3, 0xb1, 0xce, 0xba, 0xfb, 0xdb, 0xbc, 0x60, 0x77, 0xc0, 0xb1, 0xad, 0x1d, 0xf5, 0x1b, 0xa0, 0x03, 0x09, 0x70, 0xe1, 0xdd, 0x64, 0x31 +}; + +int main( int argc, char** argv ) +{ + try + { + fc::ecc::compact_signature fc_canon_sig; + memcpy( fc_canon_sig.data, fc_canon, sizeof( unsigned char ) * 65 ); + + fc::ecc::compact_signature bip_0062_canon_sig; + memcpy( bip_0062_canon_sig.data, bip_0062_canon, sizeof( unsigned char ) * 65 ); + + fc::ecc::compact_signature bip_0062_canon2_sig; + memcpy( bip_0062_canon2_sig.data, bip_0062_canon2, sizeof( unsigned char ) * 65 ); + + fc::ecc::compact_signature non_canon_sig; + memcpy( non_canon_sig.data, non_canon, sizeof( unsigned char ) * 65 ); + + ilog( "Testing non-canonical validation" ); + FC_ASSERT( fc::ecc::public_key::is_canonical( fc_canon_sig, fc::ecc::canonical_signature_type::non_canonical ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( bip_0062_canon_sig, fc::ecc::canonical_signature_type::non_canonical ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( bip_0062_canon2_sig, fc::ecc::canonical_signature_type::non_canonical ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( non_canon_sig, fc::ecc::canonical_signature_type::non_canonical ) ); + + ilog( "Testing bip_0062 canonical validation" ); + FC_ASSERT( fc::ecc::public_key::is_canonical( fc_canon_sig, fc::ecc::canonical_signature_type::bip_0062 ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( bip_0062_canon_sig, fc::ecc::canonical_signature_type::bip_0062 ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( bip_0062_canon2_sig, fc::ecc::canonical_signature_type::bip_0062 ) ); + FC_ASSERT( !fc::ecc::public_key::is_canonical( non_canon_sig, fc::ecc::canonical_signature_type::bip_0062 ) ); + + ilog( "Testing fc canonical validation" ); + FC_ASSERT( fc::ecc::public_key::is_canonical( fc_canon_sig, fc::ecc::canonical_signature_type::fc_canonical ) ); + FC_ASSERT( !fc::ecc::public_key::is_canonical( bip_0062_canon_sig, fc::ecc::canonical_signature_type::fc_canonical ) ); + FC_ASSERT( fc::ecc::public_key::is_canonical( bip_0062_canon2_sig, fc::ecc::canonical_signature_type::fc_canonical ) ); + FC_ASSERT( !fc::ecc::public_key::is_canonical( non_canon_sig, fc::ecc::canonical_signature_type::fc_canonical ) ); + } + catch( fc::exception& e ) + { + ilog( "Uncaught Exception: ${e}", ("e", e) ); + return 1; + } + + return 0; +}