WASM runs inside a Lit Action
The Lit Action runtime is Deno-based, so the standardWebAssembly API is
available alongside the web platform globals an action already has (fetch,
CompressionStream, crypto, TextEncoder, …). That means you can run a
WebAssembly module directly inside the action — no native add-ons, no separate
service. Anything that compiles to wasm (Rust, C/C++, Go, AssemblyScript) and
anything published as a wasm-bindgen package on npm works.
This is what makes heavyweight, audited cryptography practical inside an action:
the mpc-signing-ecdsa example
runs the DKLs23 threshold-ECDSA protocol (a Trail-of-Bits-audited
Rust library compiled to wasm) entirely inside the action to co-sign with the user,
and the mpc-signing-frost example
does the same with threshold FROST (the Kudelski-audited lit-frost + frost-dkg,
compiled to wasm) for Schnorr/EdDSA chains like Solana.
Loading a module
There are two ways to get the wasm bytes into the runtime.1. Import the glue, fetch the wasm at runtime
Most wasm-bindgen packages ship a small JS “glue” module plus a.wasm binary.
Import the glue from jsDelivr (pinned + SHA-384 verified like any
other import), then fetch the .wasm bytes and hand them to initSync:
2. Inline the wasm as base64
For maximum trust, base64-encode the.wasm and embed it in the action source,
then decode and initSync it. This removes the runtime fetch and makes the
action’s IPFS CID commit to the exact crypto bytes — there is no external
dependency to resolve at run time:
jsDelivr is immutable at a pinned version and integrity-checked, so option 1 is
safe for most uses. Option 2 is the tighter setup when you want the CID itself
to attest to the precise bytes that ran (e.g. so a verifier doesn’t have to
trust the CDN at all). The trade-off is action size — a large module inlined as
base64 grows the source ~33%, and an action that exceeds the request-body limit
can’t be submitted at all.Middle ground (option 1 + a pinned hash). When the module is too big to
inline but you still want the CID to commit to the crypto, fetch the wasm and
verify its SHA-256 against a constant in the action before The mpc-signing-frost example uses exactly this — its ~1.5 MB FROST module is too
large to inline, so it pins the hash instead.
initSync-ing it —
the CID commits to the hash, so the action refuses to run any other bytes:Things to keep in mind
- It’s Deno, not Node. You get web APIs (
fetch, streams,WebAssembly,crypto), not Node built-ins. Use the web build of a wasm-bindgen package (e.g.…-web), not the…-nodebuild. - Size and response limits. A big module plus its working state count against action size and the response-payload cap — see Limits. The mpc-signing-ecdsa example relays a large sealed session each round, well within the default response cap.
- Stateless across calls. An action holds no memory between invocations, so a
wasm session that must span multiple calls has to be serialized out and passed
back in (mpc-signing-ecdsa seals its session with
Lit.Actions.Encryptand relays it through the user each round).
See it run
The mpc-signing-ecdsa example
runs DKLs23 threshold ECDSA in wasm inside the action: it instantiates the wasm,
serializes and rebuilds the module’s session between every protocol round (the
stateless-relay pattern), and produces a signature plain
ecrecover accepts. Its
action/mpcSigner.js is a working template for getting any wasm module running in
an action.