diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 2c15f91f585..e2395c573f4 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -269,6 +269,7 @@ struct controller_impl { set_activation_handler(); set_activation_handler(); set_activation_handler(); + set_activation_handler(); self.irreversible_block.connect([this](const block_state_ptr& bsp) { wasmif.current_lib(bsp->block_num); @@ -3465,6 +3466,13 @@ void controller_impl::on_activation( } ); } +template<> +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "recover_key_safe" ); + } ); +} + /// End of protocol feature activation handlers } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index 846d71de5a4..406794e20ce 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -29,7 +29,8 @@ enum class builtin_protocol_feature_t : uint32_t { blockchain_parameters, security_group, resource_payer, - get_wasm_parameters_packed_fix + get_wasm_parameters_packed_fix, + recover_key_safe, }; struct protocol_feature_subjective_restrictions { diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp index 6e492c4659c..fd5f00d5ac0 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp @@ -276,7 +276,8 @@ constexpr auto intrinsic_table = boost::hana::make_tuple( "env.add_security_group_participants"_s, "env.remove_security_group_participants"_s, "env.in_active_security_group"_s, - "env.get_active_security_group"_s, + "env.get_active_security_group"_s, + "env.recover_key_safe"_s, // the following should always be in the end of the tuple "env.push_data"_s, "env.print_time_us"_s, diff --git a/libraries/chain/include/eosio/chain/webassembly/error_codes.hpp b/libraries/chain/include/eosio/chain/webassembly/error_codes.hpp new file mode 100644 index 00000000000..32b32b48a59 --- /dev/null +++ b/libraries/chain/include/eosio/chain/webassembly/error_codes.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace eosio { namespace chain { namespace webassembly { namespace error_codes { + + enum recover_key_safe : int32_t { + undefined = -1, ///< undefined error + none = 0, ///< succeed + invalid_message_digest, ///< failed to deserialize a message digest + invalid_signature_format, ///< failed to deserialize a signature + unactivated_key_type, ///< failed to recover a key using unactivated key algorithm + invalid_signature_data, ///< failed to recover a key from the given signature data + insufficient_output_buffer, ///< failed to store a recovered key due to insufficient output buffer + }; + +}}}} // ns eosio::chain::webassembly::error_codes diff --git a/libraries/chain/include/eosio/chain/webassembly/interface.hpp b/libraries/chain/include/eosio/chain/webassembly/interface.hpp index 4cd6c306459..758a5202b77 100644 --- a/libraries/chain/include/eosio/chain/webassembly/interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/interface.hpp @@ -338,6 +338,20 @@ namespace webassembly { */ int32_t recover_key(legacy_ptr digest, legacy_span sig, legacy_span pub) const; + /** + * Calculates the public key used for a given signature on a given digest. + * Unlike @ref recover_key it returns an error code rather than throws an exception with invalid input. + * + * @ingroup crypto + * @param digest - digest of the message that was signed. + * @param sig - signature. + * @param[inout] pub - pointer to output buffer for the public key result. + * @param[out] publen - pointer to the size of output buffer of the public key pub. This value can be used when error_code::insufficient_output_buffer is returned to determine required size of pub. + * + * @return 0 when success, or error code + */ + int32_t recover_key_safe(span digest, span sig, span pub, uint32_t* publen) const; + /** * Tests if the sha256 hash generated from data matches the provided digest. * diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index 3e8fe24f4c2..7fd067f4134 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -261,6 +261,17 @@ Builtin protocol feature: GET_WASM_PARAMETERS_PACKED_FIX Fix an issue with the host function get_wasm_parameters_packed, due to miscalculating the required size for the packed output data. +*/ + {} + } ) + (builtin_protocol_feature_t::recover_key_safe, builtin_protocol_feature_spec{ + "RECOVER_KEY_SAFE", + fc::variant("80f1baa2605aec0a7a797b386cdea5c4b612a51d406de9ab098c9b0b5f70843d").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: RECOVER_KEY_SAFE + +Adds an alternative intrinsic for recover_key that doesn't throw an exception with invalid input. */ {} } ) diff --git a/libraries/chain/webassembly/crypto.cpp b/libraries/chain/webassembly/crypto.cpp index 890cc9ad5dc..e2a9bfee62d 100644 --- a/libraries/chain/webassembly/crypto.cpp +++ b/libraries/chain/webassembly/crypto.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace eosio { namespace chain { namespace webassembly { @@ -65,6 +66,57 @@ namespace eosio { namespace chain { namespace webassembly { } } + int32_t interface::recover_key_safe( span digest, + span sig, + span pub, + uint32_t* publen) const { + using error_code = eosio::chain::webassembly::error_codes::recover_key_safe; + + try { + fc::crypto::signature s; + try { + datastream ds( sig.data(), sig.size() ); + fc::raw::unpack(ds, s); + } catch ( fc::exception& ) { + return error_code::invalid_signature_format; + } + + if( static_cast(s.which()) >= context.db.get().num_supported_key_types ) { + return error_code::unactivated_key_type; + } + + if(context.control.is_producing_block()) + EOS_ASSERT(s.variable_size() <= context.control.configured_subjective_signature_length_limit(), + sig_variable_size_limit_exception, "signature variable length component size greater than subjective maximum"); + + fc::sha256 _digest; + try { + _digest = fc::sha256(digest.data(), digest.size()); + } catch ( fc::exception& ) { + return error_code::invalid_message_digest; + } + + fc::crypto::public_key recovered; + try { + recovered = fc::crypto::public_key(s, _digest, false); + } catch ( fc::exception& ) { + return error_code::invalid_signature_data; + } + + auto packed_pubkey = fc::raw::pack(recovered); + if( pub.size() < packed_pubkey.size() ) { + return error_code::insufficient_output_buffer; + } + std::memcpy(pub.data(), packed_pubkey.data(), packed_pubkey.size()); + *publen = packed_pubkey.size(); + } catch( const eosio::chain::sig_variable_size_limit_exception& ) { + throw; + } catch ( fc::exception& ) { + return error_code::undefined; + } + return error_code::none; + } + void interface::assert_sha256(legacy_span data, legacy_ptr hash_val) const { auto result = context.trx_context.hash_with_checktime( data.data(), data.size() ); EOS_ASSERT( result == *hash_val, crypto_api_exception, "hash mismatch" ); diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index fb169a7f6e7..de3c2aaecba 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -280,6 +280,7 @@ REGISTER_LEGACY_HOST_FUNCTION(get_active_producers); // crypto api REGISTER_LEGACY_CF_HOST_FUNCTION(assert_recover_key); REGISTER_LEGACY_CF_HOST_FUNCTION(recover_key); +REGISTER_CF_HOST_FUNCTION(recover_key_safe); REGISTER_LEGACY_CF_HOST_FUNCTION(assert_sha256); REGISTER_LEGACY_CF_HOST_FUNCTION(assert_sha1); REGISTER_LEGACY_CF_HOST_FUNCTION(assert_sha512);