crypto: add recover_key_nothrow intrinsic + CDT bindings#60
Conversation
CDT contracts compile with -fno-exceptions, so the throwing recover_key intrinsic halts the WASM on attacker-controlled signature bytes — fatal inside any handler that runs in the evalcons inline-action chain (e.g. sysio.uwrit::try_select_winner), because a halt stalls consensus. This adds a recover_key_nothrow variant that returns std::nullopt on any failure path (malformed bytes, unactivated sig type, recovery math failure, subjective-size limit) instead of throwing. Wired through: - imports/cdt.imports.in — declare the intrinsic name for the WASM importer. - libraries/sysiolib/capi/sysio/crypto.h — C-API entry with the WASM import attribute + full Doxygen. - libraries/sysiolib/crypto.cpp — C++ wrapper returning std::optional<sysio::public_key>, mirroring the existing recover_key buffer-resize loop but binding the no-throw intrinsic. - libraries/sysiolib/core/sysio/crypto.hpp — header declaration + <optional> include. - libraries/native/intrinsics.cpp + intrinsics_def.hpp — native-shim entry so test contracts running on the native runtime resolve the symbol. The host-side wrapping (try/catch around recover_key) lives in wire-sysio. Used at the depot by sysio.uwrit::verify_uic_signature on the underwriter race path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
||
| char optimistic_pubkey_data[256]; | ||
| int rc = ::recover_key_nothrow( | ||
| reinterpret_cast<const capi_checksum256*>(digest_data.data()), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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
|
Went with #59 instead. |
Summary
recover_key_nothrowintrinsic and CDT binding alongside the existingrecover_key. Returns-1(C-API) /std::nullopt(C++ wrapper) instead of throwing on any host-side failure (malformed signature bytes, unactivated signature type, recovery math failure, subjective-size limit).-fno-exceptions, so the throwingrecover_keyhalts the WASM on attacker-controlled signature bytes — fatal inside any handler that runs in theevalconsinline-action chain (e.g.sysio.uwrit::try_select_winner), where a halt stalls consensus.imports/cdt.imports.in,libraries/sysiolib/capi/sysio/crypto.h,libraries/sysiolib/crypto.cpp,libraries/sysiolib/core/sysio/crypto.hpp, plus the native-shim entries inlibraries/native/intrinsics.cppandintrinsics_def.hppso test contracts on the native runtime resolve the symbol.The matching host-side
try/catchwrapper aroundrecover_keylives inwire-sysio. Consumed at the depot bysysio.uwrit::verify_uic_signatureon the underwriter race path.Test plan
cmake --build build) and confirm the new intrinsic links.recover_key_nothrow(e.g.sysio.uwrit) and verify it importsrecover_key_nothrowin the resulting WASM.recover_key_nothrowwith both a valid signature and an intentionally malformed one; expect a recovered key in the first case andstd::nullopt(no halt) in the second.🤖 Generated with Claude Code