Skip to content

guest-sdk: rework the public interface from external feedback - #57

Merged
lann merged 5 commits into
mainfrom
webcrypto-api-feedback
Jul 27, 2026
Merged

guest-sdk: rework the public interface from external feedback#57
lann merged 5 commits into
mainfrom
webcrypto-api-feedback

Conversation

@lann

@lann lann commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Reworks the lann-webcrypto-guest public interface per external review feedback. No WIT changes; the component ABI and all cross-implementation behavior are untouched.

Changes

  • rawbindings: the generated-bindings escape hatch follows ecosystem convention (wasip3-style). from_raw/as_raw/into_raw keep their names.
  • mint flattened into the root: lann_webcrypto_guest::hmac_sha2::generate_key(...) etc. — one root module per algorithm interface, mirroring the WIT layout, no more jargon module name.
  • Newtype names: MacKeyMac, AeadKeyAead, InternalNonceKeyAeadInternalNonce. SigningKey/VerifyingKey deliberately keep Key: the RustCrypto precedent for stripping it (hmac::Mac, aead::Aead) applies to algorithm-instance objects, while ed25519-dalek and the ecdsa crate keep exactly these signature-key names.
  • Crate-owned #[non_exhaustive] Error mirroring the WIT variants plus an unconditional Read(std::io::Error) variant for failing local producers (the variant is unconditional — only its producer is feature-gated — to avoid the feature-unification semver trap). From<bindings::types::Error>, Display, Error::source.
  • Generalized inputs: operations take impl Into<DataSource>&[u8]/&[u8; N]/Vec<u8>/&Vec<u8> (buffered), StreamReader<u8> (passed through without a local feeder), DataSource::from_buf (feature bytes), DataSource::from_reader (feature futures-io). Blanket impls over Buf/AsyncRead are impossible (&[u8] implements both), hence explicit adapters. A reader failure wins over the operation's own result, which was computed over a truncated input.
  • seal/open return StreamReader<u8>: trivial to collect().await, and unlocks forwarding without buffering. open's docs carry the contract that the stream is handed back only after the tag verifies.
  • Docs: algorithm_* getters link the W3C WebCrypto algorithm registry and name the projected spec fields; security-critical WIT contracts (nonce uniqueness, fail-closed verify, unverified-plaintext-never-observable, truncating producers) are duplicated into the method docs; WIT-style refs replaced with intra-doc links; pub use wit_bindgen; (wasip3 precedent).
  • constant_time_equal stays a component import (considered and rejected switching to an in-guest constant_time_eq re-export): the import lets the host perform the comparison in native code where constant-time properties actually hold; the ABI transit is negligible. Now documented on the function.

Consumers (examples/crypto-demo, conformance/guest, conformance/signing-guest) updated; crypto-demo's hmac-key-export check now exercises the new wrapper layer.

Advances (does not close) the guest-side convenience libraries item of #35.

Verification

  • just check — pass (fmt, clippy incl. wasm target, validate-wit, tests incl. guest-under-Wasmtime)
  • just test-webcrypto-composed — 22/22 checks
  • just conformance-wasmtime + just conformance-composed — 6226 cases + 5 signing cases, 0 failed
  • just transpile — pass
  • cargo check/clippy/doc on wasm32-wasip2 under all four feature combos — clean
  • jco-node/jco-browser conformance could not run locally (Node 20 here; JSPI needs Node 24+) — relying on CI. Risk is low: the WIT surface and component ABI are unchanged.

- rename the raw module to bindings; flatten mint into root
  per-algorithm modules
- strip Key from the algorithm-instance newtypes (Mac, Aead,
  AeadInternalNonce) per RustCrypto precedent; SigningKey/VerifyingKey
  keep their names (also RustCrypto precedent)
- introduce a crate-owned non_exhaustive Error mirroring the WIT
  variants plus Read(std::io::Error) for failing local producers
- operations take impl Into<DataSource>: slices, owned buffers,
  StreamReader<u8> pass-through, bytes::Buf (feature bytes), and
  futures_io::AsyncRead (feature futures-io)
- seal/open return the underlying StreamReader<u8>; collect() when you
  want the bytes
- re-export wit_bindgen (wasip3 precedent) and StreamReader
- docs: link the WebCrypto algorithm registry from the algorithm-*
  getters, duplicate security-critical WIT contracts into method docs,
  use intra-doc links instead of WIT-style refs, and document why
  constant-time-equal stays a component import
lann added 4 commits July 27, 2026 18:44
# Conflicts:
#	conformance/signing-guest/src/lib.rs
Module layout: generated, bindings, and the nine per-algorithm creation
modules move to their own files; lib.rs keeps the error, data-source,
and newtype layers.

Copy discipline, in the same spirit throughout:
- DataSource is lifetime-parameterized over Cow<'a, [u8]>: borrowed
  slices stay borrowed until fed (the ABI's write_all needs an owned
  buffer, so that single copy is deferred to the feed), and Vec<u8>
  moves without copying. From<Cow<'a, [u8]>> added.
- from_buf/from_reader accept borrowing sources (dyn Buf + 'a /
  dyn AsyncRead + 'a instead of 'static), and their feed loops reuse
  one scratch buffer across chunks: write_all returns its argument's
  allocation, so each chunk costs the one unavoidable copy and no
  allocation.
- The list<u8> parameters (nonce, aad, tag, sig) take
  impl Into<Cow<'_, [u8]>>: slices still work unchanged, and owned
  vectors are moved instead of forced through to_vec — aad in
  particular can be large.
The per-algorithm creation modules keep their own files; the bindgen
invocation and its re-export surface are two dozen lines that belong
next to the crate docs describing them.
The Cow representation only deferred the borrowed-input copy; it did
not eliminate it, since write_all needs an owned buffer. Make it earn
its keep: feed Cow::Borrowed data in chunks through the same reusable
scratch-buffer pattern as the Buf and Reader sources, so a large
borrowed input costs one chunk of extra memory instead of a whole
duplicate. Owned data is still written whole with zero copies.

The demo's wrapper check now round-trips a payload spanning several
feed chunks, covering the incremental path on every target.
@lann
lann merged commit 33f3d62 into main Jul 27, 2026
4 checks passed
@lann
lann deleted the webcrypto-api-feedback branch July 27, 2026 23:45
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.

1 participant