docs: add WASM_BUILD.md explaining pkg generation and frontend loading

Covers:
- What antibot_wasm.js is and why it is not in the repo
- How wasm-pack compiles wasm/src/ and what wasm/pkg/ contains
- Step-by-step build (rustup target, wasm-pack install, build, mv to frontend/pkg)
- How heartbeat.js loads and initialises the module via await init()
- MIME type requirements for serving .wasm files
- .gitignore rationale for wasm/pkg/ and frontend/pkg/
- Troubleshooting (missing target, wasm-opt, 404, empty string returns)
This commit is contained in:
thakares committed 2026-05-09 18:17:53 +05:30
1 parent 2840ddfc58
commit 256f12023e
1 file changed
+301
+301
View File
@@ -0,0 +1,301 @@
# ChronoSeal — WASM Build Guide
## Overview
The client-side cryptographic core of ChronoSeal is written in Rust and
compiled to WebAssembly (WASM). The JavaScript frontend (`heartbeat.js`)
imports functions from this WASM module to generate keypairs, sign heartbeat
payloads, compute hash chain links, and execute the stack machine program.
The import line in `heartbeat.js`:
```js
import init, { generate_keypair, sign_message, compute_next_hash, run_program }
from './pkg/antibot_wasm.js';
```
`./pkg/antibot_wasm.js` is a **generated file**. It does not exist in the
repository and must be produced by building the `wasm/` crate before running
the server.
---
## How the WASM Module is Built
The tool that compiles Rust to WASM and generates the JavaScript glue is
[`wasm-pack`](https://rustwasm.github.io/wasm-pack/).
When you run:
```bash
wasm-pack build wasm --target web --release
```
wasm-pack does the following in sequence:
1. Compiles `wasm/src/lib.rs` (and its submodules) to a `.wasm` binary using
the `wasm32-unknown-unknown` target.
2. Runs `wasm-bindgen` to inspect every `#[wasm_bindgen]`-annotated function
and struct and generate a JavaScript wrapper for each one.
3. Optionally runs `wasm-opt` (from Binaryen) to size-optimise the binary.
4. Writes all output to `wasm/pkg/`.
---
## Output: `wasm/pkg/`
After a successful build, `wasm/pkg/` contains:
```
wasm/pkg/
├── antibot_wasm.js ← ES module; the file heartbeat.js imports
├── antibot_wasm_bg.wasm ← compiled WASM binary (~300–800 KB release)
├── antibot_wasm_bg.js ← internal memory bridge (do not import directly)
├── antibot_wasm.d.ts ← TypeScript type declarations
├── antibot_wasm_bg.d.ts ← TypeScript declarations for the bg module
└── package.json
```
### `antibot_wasm.js`
This is the public entry point. It contains:
- An `init()` function that fetches and instantiates the `.wasm` binary.
- One JavaScript wrapper function for each `#[wasm_bindgen]` export in
`wasm/src/`:
| Rust export | JS wrapper | Description |
|---|---|---|
| `generate_keypair()` | `generate_keypair()` | Generate Ed25519 keypair; return hex public key |
| `get_public_key()` | `get_public_key()` | Return hex public key, or `""` if not initialised |
| `sign_message(msg)` | `sign_message(msg)` | Sign string; return hex signature, or `""` if not initialised |
| `compute_next_hash(prev, ts, entropy, stack, salt)` | `compute_next_hash(...)` | Compute next Blake3 chain hash |
| `run_program(b64)` | `run_program(b64)` | Execute base64 VM program; return `{ stack, ip }` |
### `antibot_wasm_bg.wasm`
The compiled binary. The `.bg` suffix means "background" — this is the raw
WASM that `antibot_wasm.js` loads internally. You should not reference this
file directly in your HTML.
---
## Step-by-Step Build
### 1. Install the Rust WASM target
```bash
rustup target add wasm32-unknown-unknown
```
This is a one-time step. Without it, the Rust compiler cannot produce WASM
output.
### 2. Install wasm-pack
```bash
cargo install wasm-pack
```
Or via the installer script:
```bash
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```
Verify:
```bash
wasm-pack --version
# wasm-pack 0.13.x
```
### 3. Build the WASM module
From the project root:
```bash
wasm-pack build wasm --target web --release
```
`--target web` produces an ES module (`import`/`export` syntax) suitable for
use directly in a browser without a bundler. Other targets (`bundler`,
`nodejs`, `no-modules`) produce different output formats and are not
compatible with the ChronoSeal frontend as written.
`--release` enables Rust's release optimisations (inlining, dead code
elimination, size reduction). Omit it during development for faster builds
and better panic messages.
### 4. Move the output to the frontend
```bash
rm -rf frontend/pkg
mv wasm/pkg frontend/pkg
```
The frontend expects the WASM module at `frontend/pkg/antibot_wasm.js`
because `heartbeat.js` imports from `./pkg/antibot_wasm.js` relative to
the `frontend/` directory, which is where the server's static file handler
is rooted.
---
## Using the Build Script
The convenience script at `scripts/build.sh` performs all steps in order:
```bash
bash scripts/build.sh
```
This builds the WASM module, moves it to `frontend/pkg/`, and then builds
the server binary. Run this for a clean full build before deployment.
For development iteration where you are only changing Rust WASM code:
```bash
wasm-pack build wasm --target web # (omit --release for speed)
rm -rf frontend/pkg && mv wasm/pkg frontend/pkg
```
For development where you are only changing server code:
```bash
cargo build -p server
```
---
## How `heartbeat.js` Loads the Module
`heartbeat.js` uses a standard ES module dynamic import pattern:
```js
import init, { generate_keypair, sign_message, compute_next_hash, run_program }
from './pkg/antibot_wasm.js';
export async function initHeartbeat() {
// 1. Fetch and instantiate the .wasm binary
await init();
// 2. Generate keypair — private key stored in WASM memory only
const pubKeyHex = generate_keypair();
// 3. Send public key to server, receive session_id and chain seed
// ...
}
```
`init()` is the default export from `antibot_wasm.js`. It fetches
`antibot_wasm_bg.wasm` (from the same `pkg/` directory) via `fetch()`,
compiles it in the browser's WASM engine, and links it to the JS glue
layer. After `await init()` returns, all the named exports
(`generate_keypair`, `sign_message`, etc.) are ready to call.
The `init()` call must complete before any other WASM function is called.
Calling `sign_message()` or `compute_next_hash()` before `await init()`
returns will produce an empty string (the module is not yet instantiated).
---
## Serving the WASM Binary
Browsers require WASM files to be served with the correct MIME type:
```
Content-Type: application/wasm
```
Most web servers set this automatically for `.wasm` files. If you see the
error:
```
WebAssembly.instantiate(): Response has unsupported MIME type
```
Add the MIME type to your server configuration:
**nginx:**
```nginx
types {
application/wasm wasm;
}
```
**Apache `.htaccess`:**
```apache
AddType application/wasm .wasm
```
The Axum `ServeDir` handler used by ChronoSeal's built-in static server
sets the correct MIME type automatically via `tower-http`.
---
## What Is Not in the Repository
| Path | Why excluded |
|---|---|
| `wasm/pkg/` | Generated build output — changes on every build |
| `frontend/pkg/` | Same generated output, moved to serve location |
| `target/` | Standard Rust build artefacts |
Both `wasm/pkg/` and `frontend/pkg/` are listed in `.gitignore`. Committing
them would bloat the repository (the `.wasm` binary alone is 300–800 KB),
create noisy diffs on every rebuild, and give a false impression that the
WASM module is pre-built and ready to use without a build step.
---
## Troubleshooting
### `wasm32-unknown-unknown` target not found
```
error[E0463]: can't find crate for `std`
```
Fix:
```bash
rustup target add wasm32-unknown-unknown
```
### `wasm-pack` not found
```bash
cargo install wasm-pack
```
### `wasm-opt` not found (warning, not an error)
wasm-pack prints a warning if `wasm-opt` is not installed. The build still
succeeds; the binary is just not size-optimised.
```bash
# On Debian/Ubuntu/Arch
sudo apt install binaryen # Debian/Ubuntu
sudo pacman -S binaryen # Arch
```
### `antibot_wasm_bg.wasm` fetch fails (404)
The `.wasm` file is not being served from `frontend/pkg/`. Verify:
```bash
ls /mnt/Programs/ChronoSeal/frontend/pkg/
# Should list: antibot_wasm.js antibot_wasm_bg.wasm ...
```
If the directory is empty or missing, re-run the build steps above.
### MIME type error in browser
See the "Serving the WASM Binary" section above.
### `sign_message` or `generate_keypair` returns empty string
The WASM keypair has not been initialised. Ensure `await init()` and
`generate_keypair()` are called (and awaited) before any other WASM
function. Check the browser console for any errors during `init()`.