Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions imports/cdt.imports.in
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ publication_time
read_action_data
read_transaction
recover_key
recover_key_nothrow
remove_security_group_participants
require_auth
require_auth2
Expand Down
3 changes: 3 additions & 0 deletions libraries/native/intrinsics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ extern "C" {
int recover_key( const capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen ) {
return intrinsics::get().call<intrinsics::recover_key>(digest, sig, siglen, pub, publen);
}
int recover_key_nothrow( const capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen ) {
return intrinsics::get().call<intrinsics::recover_key_nothrow>(digest, sig, siglen, pub, publen);
}
void assert_sha256( const char* data, uint32_t length, const capi_checksum256* hash ) {
return intrinsics::get().call<intrinsics::assert_sha256>(data, length, hash);
}
Expand Down
1 change: 1 addition & 0 deletions libraries/native/native/sysio/intrinsics_def.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ intrinsic_macro(preactivate_feature) \
intrinsic_macro(get_active_producers) \
intrinsic_macro(assert_recover_key) \
intrinsic_macro(recover_key) \
intrinsic_macro(recover_key_nothrow) \
intrinsic_macro(assert_sha256) \
intrinsic_macro(assert_sha1) \
intrinsic_macro(assert_sha512) \
Expand Down
26 changes: 26 additions & 0 deletions libraries/sysiolib/capi/sysio/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,32 @@ void ripemd160( const char* data, uint32_t length, struct capi_checksum160* hash
__attribute__((sysio_wasm_import))
int recover_key( const struct capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen );

/**
* Non-throwing variant of `recover_key`. Catches every exception the
* host crypto path can raise (malformed signature bytes, unactivated
* signature type, recovery math failure, subjective-size limit, etc.)
* and returns -1 instead of halting the contract. On success returns
* the same byte count as `recover_key`.
*
* CDT contracts compile with `-fno-exceptions`, so a throwing
* intrinsic call from inside a contract halts dispatch — there is no
* try/catch around it. Inbound-attestation handlers
* (`feedback_opp_handlers_never_throw.md`) MUST NOT halt; they need
* this nothrow variant when verifying attacker-controlled signature
* bytes (e.g. the underwriter race resolver in `sysio.uwrit`).
*
* @param digest - Pre-hashed message to recover against
* @param sig - Packed signature bytes (sysio::signature wire format)
* @param siglen - Signature byte count
* @param pub - Destination buffer for the recovered packed public key
* @param publen - Capacity of `pub` in bytes
*
* @return Bytes written to `pub` on success; -1 on any host-side error
* (no exception escapes the call).
*/
__attribute__((sysio_wasm_import))
int recover_key_nothrow( const struct capi_checksum256* digest, const char* sig, size_t siglen, char* pub, size_t publen );

/**
* Tests a given public key with the generated key from digest and the signature.
*
Expand Down
19 changes: 19 additions & 0 deletions libraries/sysiolib/core/sysio/crypto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "serialize.hpp"

#include <array>
#include <optional>

namespace sysio {

Expand Down Expand Up @@ -453,6 +454,24 @@ namespace sysio {
*/
sysio::public_key recover_key( const sysio::checksum256& digest, const sysio::signature& sig );

/**
* Non-throwing variant of `recover_key`. Returns the recovered key on
* success, `std::nullopt` if the host caught any exception (malformed
* signature bytes, unactivated signature type, recovery math failure,
* subjective-size limit, etc.). Use this from CDT contracts that MUST
* NOT halt on attacker-controlled signature bytes — see
* `feedback_opp_handlers_never_throw.md`. CDT compiles with
* `-fno-exceptions` so the throwing variant cannot be wrapped in
* contract-side `try/catch`; the host-side wrapper catches instead.
*
* @ingroup crypto
* @param digest - Digest of the message that was signed
* @param sig - Signature
* @return Recovered public key, or `std::nullopt` on any failure.
*/
std::optional<sysio::public_key> recover_key_nothrow( const sysio::checksum256& digest,
const sysio::signature& sig );

/**
* Tests a given public key with the recovered public key from digest and signature.
*
Expand Down
44 changes: 44 additions & 0 deletions libraries/sysiolib/crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ extern "C" {
int recover_key( const capi_checksum256* digest, const char* sig,
size_t siglen, char* pub, size_t publen );

__attribute__((sysio_wasm_import))
int recover_key_nothrow( const capi_checksum256* digest, const char* sig,
size_t siglen, char* pub, size_t publen );

__attribute__((sysio_wasm_import))
void assert_recover_key( const capi_checksum256* digest, const char* sig,
size_t siglen, const char* pub, size_t publen );
Expand Down Expand Up @@ -121,6 +125,46 @@ namespace sysio {
return pubkey;
}

std::optional<sysio::public_key> recover_key_nothrow( const sysio::checksum256& digest,
const sysio::signature& sig ) {
auto digest_data = digest.extract_as_byte_array();
auto sig_data = sysio::pack(sig);

char optimistic_pubkey_data[256];
int rc = ::recover_key_nothrow(
reinterpret_cast<const capi_checksum256*>(digest_data.data()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repeats the misaligned digest cast pattern in the new recover_key_nothrow path. capi_checksum256 is declared aligned(16), but digest.extract_as_byte_array().data() is byte-array storage and is not guaranteed to be 16-byte aligned. Forming a const capi_checksum256* from it via reinterpret_cast is not portable and can be UB or trap on stricter/native targets. Prefer creating a local aligned capi_checksum256 via std::bit_cast or memcpy, then pass &capi_digest. Same applies to the refill call at line 152.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huangminghuang it just duplicated what was there for recover_key! Also, for something like this where it works, just isn't ideal, you are welcome to fix/change. that said, we're using the other impl from @heifner PR

sig_data.data(), sig_data.size(),
optimistic_pubkey_data, sizeof(optimistic_pubkey_data) );
if ( rc < 0 ) return std::nullopt;

const size_t pubkey_size = static_cast<size_t>(rc);
sysio::public_key pubkey;
if ( pubkey_size <= sizeof(optimistic_pubkey_data) ) {
sysio::datastream<const char*> pubkey_ds( optimistic_pubkey_data, pubkey_size );
pubkey_ds >> pubkey;
} else {
constexpr static size_t max_stack_buffer_size = 512;
void* pubkey_data = (max_stack_buffer_size < pubkey_size)
? malloc(pubkey_size)
: alloca(pubkey_size);

int rc2 = ::recover_key_nothrow(
reinterpret_cast<const capi_checksum256*>(digest_data.data()),
sig_data.data(), sig_data.size(),
reinterpret_cast<char*>(pubkey_data), pubkey_size );
if ( rc2 < 0 ) {
if ( max_stack_buffer_size < pubkey_size ) free(pubkey_data);
return std::nullopt;
}
sysio::datastream<const char*> pubkey_ds(
reinterpret_cast<const char*>(pubkey_data), pubkey_size );
pubkey_ds >> pubkey;

if ( max_stack_buffer_size < pubkey_size ) free(pubkey_data);
}
return pubkey;
}

void assert_recover_key( const sysio::checksum256& digest, const sysio::signature& sig, const sysio::public_key& pubkey ) {
auto digest_data = digest.extract_as_byte_array();

Expand Down