Rust -> Wasm bindings
This guide explains how to add new WASM bindings for Rust functions in the garaga_rs crate.
Overview
The WASM bindings are implemented using wasm-bindgen, which provides a way to expose Rust functions to JavaScript/TypeScript. The bindings are located in tools/garaga_rs/src/wasm_bindings.rs.
Adding a New Binding
Add your function to
tools/garaga_rs/src/wasm_bindings.rsUse the
#[wasm_bindgen]attribute to expose the functionRebuild the WASM package
Example Implementation
use wasm_bindgen::prelude::*;
use num_bigint::BigUint;
#[wasm_bindgen]
pub fn your_function(
input_hex: &str, // Hex string input
input_array: Vec<JsValue>, // Array of values
) -> Result<JsValue, JsError> {
// Parse hex string to BigUint
let value = jsvalue_to_biguint(&JsValue::from_str(input_hex))?;
// Process array elements
let values: Vec<BigUint> = input_array
.iter()
.map(|v| jsvalue_to_biguint(v))
.collect::<Result<Vec<_>, _>>()?;
// Perform computation
let result = compute_something(&value, &values)
.map_err(|e| JsError::new(&e.to_string()))?;
// Return result as BigInt string
Ok(biguint_to_jsvalue(&result))
}Type Conversions
Common conversion patterns for WASM bindings:
Error Handling
Use JsError for errors that should be visible in JavaScript:
Available Functions
The following functions are currently exposed via WASM:
msm_calldata_builder
Generate MSM calldata
mpc_calldata_builder
Generate multi-pairing check calldata
get_groth16_calldata
Generate Groth16 proof calldata
get_zk_honk_calldata
Generate ZK Honk proof calldata
schnorr_calldata_builder
Schnorr signature calldata
ecdsa_calldata_builder
ECDSA signature calldata
eddsa_calldata_builder
EdDSA signature calldata
drand_calldata_builder
drand verification calldata
poseidon_hash
Poseidon hash (BN254)
to_weirstrass
Convert Ed25519 to Weierstrass form
to_twistededwards
Convert Weierstrass to Twisted Edwards
TypeScript Wrapper
The TypeScript API in tools/npm/garaga_ts/src/node/api.ts provides a friendlier interface over the raw WASM bindings:
Development notes
The Garaga NPM package is a mixed package. It is implemented in TypeScript but also reuses Rust code targeted to WebAssembly (WASM) with the help of wasm-pack.
The src folder is organized into two subfolders: node which contains the implementation in TypeScript; and wasm which has the interoperabilty code produced by wasm-pack.
Changes to the TypeScript library should only be made to files under the node subfolder. Changes to the Rust implementation requires regenerating files under the wasm subfolder.
Onces changes are in place they can be made permanent into the repository by committing the contents of both folders. Here is the bulk of the process:
Open your terminal or command prompt.
Use
gitto clone the repository:If you make TypeScript only changes, you can quickly rebuild the package using the
build:nodeNPM script:If instead you make Rust changes, it is necessary to generate the WASM interoperability code using the
buildNPM script:However, before commiting changes, it is necessary to generate the WASM interoperability code in a reproducible manner using docker:
How wasm-pack is used to achieve interoperability
wasm-pack is used to achieve interoperabilityInternaly the build NPM script uses wasm-pack to produce the WASM interoperability code. This is achieved by running
Let's unpack it.
In the Rust source folder we run wasm-pack in --target web mode. This generates TypeScript code targeting web pages. The --release option is required to minimize the size of the WASM module. The --no-default-features ensures we start with a clean slate (no bindings), and --features wasm explicitly enables only the WASM bindings we need.
Once the wasm-pack is done, the code is generated under the folder src/wasm/pkg of garaga_ts that houses the TypeScript source code.
We then run a custom script patch.wasm.cjs which makes minimal changes to the code generated by wasm-pack to facilitate seamless support of the WASM module in both the browser and Node.js. Basically it converts the WASM module to a Base64 string that can be loaded in a portable way in both environments, amongst other minor tweaks.
(It is important to note that the use of a custom script is only required so long wasm-pack itself does not provide a more portable/universal target mode.)
Last updated
Was this helpful?