Skip to content

feat: enable WASM compilation for walletkit#362

Open
Dzejkop wants to merge 35 commits intomainfrom
feat/drop-storage-feature-rebased
Open

feat: enable WASM compilation for walletkit#362
Dzejkop wants to merge 35 commits intomainfrom
feat/drop-storage-feature-rebased

Conversation

@Dzejkop
Copy link
Copy Markdown
Contributor

@Dzejkop Dzejkop commented Apr 17, 2026

Supersedes #253 after rebasing onto the latest main and resolving the conflict set there.

Summary

Enables wasm32-unknown-unknown compilation for both walletkit-core and walletkit crates. This is the foundation for running World ID authentication flows in the browser.

What works on WASM

  • Full crate compilation (cargo check --target wasm32-unknown-unknown)
  • Authenticator::init
  • generate_nullifier / generate_credential_blinding_factor (via WASM-compatible OPRF transport upstream)
  • local proof generation paths
  • getters and storage abstractions with correct platform gating

What's native-only (correctly gated)

  • Groth16Materials::from_cache (filesystem-based, uses std::fs)
  • filesystem-backed Groth16 cache helpers
  • native TLS / ctor initialization paths

Major changes

walletkit-db WASM FFI fixes

Fixes sqlite-wasm-rs FFI compatibility and supports the WASM compile path.

Remove storage feature flag

Unifies the storage code path so the SQLite credential store is always compiled in.

Introduce Groth16Materials

Decouples Groth16 material loading from authenticator construction with:

  • from_cache for native
  • from_embedded for embedded-zkeys builds

Enable WASM compilation

  • gate native-only code paths where needed
  • update UniFFI / WASM compatibility wiring
  • split native-only backup / cache / listener exports from generic export blocks where necessary
  • keep the workspace building clean after the rebase

Upstream dependencies

This PR currently relies on git patches for upstream compatibility:

  • worldcoin/world-id-protocol (main)
  • TaceoLabs/oprf-service (main)

Verification

  • cargo fmt --all
  • cargo check --workspace
  • cargo clippy --workspace --all-targets

Dzejkop and others added 30 commits April 17, 2026 14:22
Map close_v2 wrapper to sqlite3_close on wasm and use SQLITE_TRANSIENT() for bind blob/text destructor arguments to match sqlite-wasm-rs 0.5 signatures.

Co-authored-by: otto@toolsforhumanity.com
Add a dedicated CI job that installs wasm32 target, selects a wasm-capable clang, and runs cargo check for walletkit-db on wasm32-unknown-unknown.

Co-authored-by: otto@toolsforhumanity.com
Detect available llvm-ar variants (or fallback to ar) instead of assuming llvm-ar is on PATH when using system clang for wasm builds.

Co-authored-by: otto@toolsforhumanity.com
Co-authored-by: otto@toolsforhumanity.com
Make the SQLite-backed credential storage an always-compiled part of
walletkit-core instead of an optional feature. This unifies the code
path for native and future WASM builds.

Changes:
- Make ciborium, hkdf, rand, sha2, uuid, walletkit-db non-optional deps
- Remove `storage` feature definition from walletkit-core and walletkit
- Remove all `#[cfg(feature = "storage")]" gates from source and tests
- Remove non-storage Authenticator constructors (the ones without
  paths/store params); only storage-aware constructors remain

BREAKING CHANGE: Authenticator::init and Authenticator::init_with_defaults
now always require Arc<StoragePaths> and Arc<CredentialStore> parameters.

Co-authored-by: otto@toolsforhumanity.com
Replace the `paths: Arc<StoragePaths>` parameter in Authenticator
constructors with `materials: Arc<Groth16Materials>`, decoupling
Groth16 material loading from authenticator initialization.

Groth16Materials provides two constructors:
- `from_embedded()`: loads compiled-in zkeys/graphs (all platforms)
- `from_cache(paths)`: loads from filesystem cache (native only)

This enables WASM builds where no filesystem is available — callers
use `from_embedded()` directly instead of caching to disk first.

Also gates `groth16_cache` module behind `cfg(not(wasm32))` since
it depends on `std::fs`.

BREAKING CHANGE: Authenticator::init and init_with_defaults now take
`Arc<Groth16Materials>` instead of `Arc<StoragePaths>`.

Co-authored-by: otto@toolsforhumanity.com
Platform-gate all native-only dependencies and uniffi attributes for
WASM compatibility. Both `walletkit-core` and `walletkit` now compile
cleanly for `wasm32-unknown-unknown`.

Key changes:
- Gate `uniffi::` derives/attributes with `cfg_attr(not(wasm32), ...)`
  across all public types (~17 source files). uniffi requires Send which
  is unavailable on WASM's single-threaded model.
- Move `ctor`, `reqwest[rustls-tls,brotli]`, `rustls` to native-only
  deps. Base `reqwest` kept with just `json` feature (WASM-compatible).
- Gate `groth16_cache` module (uses std::fs), `generate_proof`
  (uses SystemTime), `fetch_inclusion_proof_with_cache` for non-WASM.
- Gate `is_connect()` check in http_request.rs (requires hyper, native only).
- Add WASM no-op doc comments on storage lock methods.

Depends on upstream WASM-compat PRs:
- worldcoin/world-id-protocol#512 (authenticator + proof crate gating)
- TaceoLabs/oprf-service#488 (gloo-net WebSocket transport for OPRF)
- worldcoin/semaphore-rs#131 (mmap-rs + lazy tree gating)

Uses `[patch.crates-io]` git refs pointing to those PR branches until
they are merged and released.

Co-authored-by: otto@toolsforhumanity.com
- Introduce `embed-zkeys` Cargo feature in walletkit-core and walletkit
  that activates world-id-core/embed-zkeys and world-id-core/zstd-compress-zkeys;
  previously these were always enabled, preventing downstream consumers from
  opting out (e.g. to reduce binary size on WASM).

- Feature-gate `Groth16Materials::from_embedded` behind `embed-zkeys`.
  `Groth16Materials::from_cache` (native filesystem) is unaffected.

- Feature-gate `storage::groth16_cache` (and the exported
  `cache_embedded_groth16_material`) behind `all(not(wasm32), embed-zkeys)`.

- Fix WASM compilation error: split the single `#[uniffi::export] impl
  Groth16Materials` block into two (one per feature/target gate) so the
  uniffi proc-macro no longer generates FFI shims that reference
  `from_cache`/`StoragePaths` on wasm32 targets.

- Fix dead-code / unused-import warnings on wasm32:
  - logger.rs: gate `struct LogEvent`, `LOG_CHANNEL`, and the
    `mpsc`/`Mutex` imports behind `#[cfg(not(target_arch = "wasm32"))]`.
  - storage/lock.rs: gate `StorageError` import behind non-wasm.

- Add `required-features = ["embed-zkeys"]` to the integration test
  targets that call `from_embedded` / `load_embedded_*` so cargo skips
  them when the feature is off.

- Fix pre-existing missing-docs error in authenticator_integration.rs and
  credential_storage_integration.rs (clippy --all-targets -D warnings).

- Update crate-level doc example to use the correct API and mark it
  `ignore` since it requires the `embed-zkeys` feature.

Co-authored-by: otto@toolsforhumanity.com
Co-authored-by: otto@toolsforhumanity.com
UniFFI constructors require owned Arc arguments across the FFI boundary,
so clippy::needless_pass_by_value cannot be acted on here. Document this
with #[expect(..., reason = ...)] so the suppression is self-explanatory
and will become a compile error if the lint ever stops firing.

Co-authored-by: otto@toolsforhumanity.com
sqlite3mc registers its cipher implementations during the very first
sqlite3_open_v2 call. When the test binary runs all 8 tests as parallel
threads, two threads can race inside that one-time initialization window
and one sees 'unknown cipher chacha20' even though chacha20 is compiled
in (CODEC_TYPE_CHACHA20).

Add init_sqlite() using std::sync::OnceLock so that exactly one thread
performs the first open; all other threads block until codec
initialization is complete before running their own test-specific code.
Called as the first statement of every test in the file.

Co-authored-by: otto@toolsforhumanity.com
docs.rs builds run without network access; world-id-core/embed-zkeys
downloads circuit files (zkeys) during the build script, so including
it in [package.metadata.docs.rs] features would cause every docs.rs
build to fail with a network error.

The crate-level example is already marked `ignore` precisely because
it calls from_embedded() which requires embed-zkeys, so removing the
feature from the docs.rs config is safe and complete.

Co-authored-by: otto@toolsforhumanity.com
from_embedded is gated behind #[cfg(feature = "embed-zkeys")] and
embed-zkeys is excluded from [package.metadata.docs.rs] features
(docs.rs has no network access for the circuit download). rustdoc
resolves intra-doc links against the features active at doc-build
time, so the backtick link produced an unresolved-link error under
RUSTDOCFLAGS=-Dwarnings.

Replace with a plain code-span so the reference remains readable in
the rendered docs without creating a dead link.

Co-authored-by: otto@toolsforhumanity.com
Replace the single init_logging function that contained scattered inline
#[cfg] blocks with three self-contained functions:

- init_logging_native  (#[cfg(not(wasm32))]): mpsc channel + background
  delivery thread.  Documents *why* the channel is needed: calling a
  UniFFI foreign callback synchronously from inside UniFFI's future-poll
  machinery (rust_call_with_out_status) causes EXC_BAD_ACCESS due to
  nested FFI frame corruption.

- init_logging_wasm    (#[cfg(wasm32)]): stores the logger in
  LOGGER_INSTANCE for direct synchronous dispatch.  Safe on WASM because
  the single-threaded cooperative runtime cannot have a UniFFI poll frame
  on the stack when a tracing event fires.

- init_logging         (ungated, #[uniffi::export]): handles the
  LOGGING_INITIALIZED idempotency guard and the shared subscriber setup,
  then dispatches to the appropriate platform impl via a single
  #[cfg]-gated call site.

Each platform's code path is now fully self-contained; the public
dispatcher contains no inline cfg blocks at all.

Co-authored-by: otto@toolsforhumanity.com
…vent circuit download

Both world-id-authenticator v0.5.2 and world-id-proof v0.5.2 ship with
default = ["embed-zkeys"], which means any package that activates them
without explicit default-features = false ends up triggering world-id-proof's
build script to download ~100 MB of circuit files from raw.githubusercontent.com.

The two paths that activated world-id-proof/embed-zkeys without our knowledge:

  1. walletkit-core -> world-id-core/authenticator
       -> dep:world-id-authenticator (default-features = true)
       -> world-id-authenticator/embed-zkeys
       -> world-id-proof/embed-zkeys                       ← download

  2. walletkit-core -> world-id-core/authenticator
       -> dep:world-id-proof (default-features = true)
       -> world-id-proof/default = ["embed-zkeys"]        ← download

Neither path is controllable from our Cargo.toml without patching upstream
crates, since both are inside the published world-id-core v0.5.2.

Fix: vendor both crates with a single change each (default = []) and add
[patch.crates-io] entries pointing to the vendor copies.  The embed-zkeys
feature still activates correctly when our own  feature is
explicitly enabled (verified: world-id-proof resolves to [embed-zkeys,
zstd-compress-zkeys] when walletkit-core/embed-zkeys is active).

The vendor copies are verbatim source from crates.io v0.5.2 with only the
[features] default line changed.  They should be updated whenever either
crate is bumped.

Co-authored-by: otto@toolsforhumanity.com
Remove the [patch.crates-io] overrides for world-id-authenticator and
world-id-proof that set default=[] to prevent circuit downloads.
The upstream crates on crates.io are used directly now.

Also fix duplicate uniffi workspace dependency in walletkit-core/Cargo.toml
introduced during rebase conflict resolution.
Otto and others added 5 commits April 17, 2026 15:52
…lock

The UniFFI proc macro processes all methods in the impl block before
`#[cfg]` attributes are evaluated, so `VaultChangedListener` was
being resolved even on wasm32 where the cfg-gated import is absent.

Move the method into the non-uniffi impl block where the `#[cfg]`
gate works as expected.
@socket-security
Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
License policy violation: cargo unicode-ident under Unicode-3.0

License: Unicode-3.0 - The applicable license policy does not permit this license (5) (unicode-ident-1.0.24/LICENSE-UNICODE)

From: ?cargo/tracing@0.1.44cargo/reqwest@0.12.28cargo/ruint@1.17.2cargo/serde_json@1.0.149cargo/uniffi@0.31.0cargo/sqlite-wasm-rs@0.5.2cargo/thiserror@2.0.18cargo/mockito@1.7.2cargo/alloy@1.7.3cargo/alloy-primitives@1.5.7cargo/alloy-core@1.5.7cargo/chrono@0.4.44cargo/rustls@0.23.37cargo/tokio@1.50.0cargo/uuid@1.22.0cargo/tempfile@3.27.0cargo/clap@4.6.0cargo/cc@1.2.57cargo/semaphore-rs@0.5.3cargo/zip@2.4.2cargo/sha2@0.10.9cargo/rand@0.8.5cargo/ciborium@0.2.2cargo/hkdf@0.12.4cargo/dirs@6.0.0cargo/ctor@0.2.9cargo/strum@0.27.2cargo/chacha20poly1305@0.10.1cargo/serde@1.0.228cargo/zeroize@1.8.2cargo/backon@1.6.0cargo/unicode-ident@1.0.24

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/unicode-ident@1.0.24. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

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