Skip to content

crypto: add recover_key_nothrow intrinsic + CDT bindings#60

Closed
jglanz wants to merge 1 commit into
masterfrom
feature/recover_key_nothrow
Closed

crypto: add recover_key_nothrow intrinsic + CDT bindings#60
jglanz wants to merge 1 commit into
masterfrom
feature/recover_key_nothrow

Conversation

@jglanz
Copy link
Copy Markdown
Collaborator

@jglanz jglanz commented May 18, 2026

Summary

  • Adds a recover_key_nothrow intrinsic and CDT binding alongside the existing recover_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).
  • CDT contracts compile with -fno-exceptions, so the throwing recover_key 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), where a halt stalls consensus.
  • Wired through 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 in libraries/native/intrinsics.cpp and intrinsics_def.hpp so test contracts on the native runtime resolve the symbol.

The matching host-side try/catch wrapper around recover_key lives in wire-sysio. Consumed at the depot by sysio.uwrit::verify_uic_signature on the underwriter race path.

Test plan

  • Build CDT (cmake --build build) and confirm the new intrinsic links.
  • Rebuild any contract that adopts recover_key_nothrow (e.g. sysio.uwrit) and verify it imports recover_key_nothrow in the resulting WASM.
  • On the native runtime, exercise a contract that calls recover_key_nothrow with both a valid signature and an intentionally malformed one; expect a recovered key in the first case and std::nullopt (no halt) in the second.

🤖 Generated with Claude Code

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>
@jglanz jglanz requested a review from heifner May 18, 2026 21:20

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

@heifner
Copy link
Copy Markdown
Contributor

heifner commented May 19, 2026

Went with #59 instead.

@heifner heifner closed this May 19, 2026
@heifner heifner deleted the feature/recover_key_nothrow branch May 19, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants