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)
8.0 KiB
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:
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.
When you run:
wasm-pack build wasm --target web --release
wasm-pack does the following in sequence:
- Compiles
wasm/src/lib.rs(and its submodules) to a.wasmbinary using thewasm32-unknown-unknowntarget. - Runs
wasm-bindgento inspect every#[wasm_bindgen]-annotated function and struct and generate a JavaScript wrapper for each one. - Optionally runs
wasm-opt(from Binaryen) to size-optimise the binary. - 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.wasmbinary. - One JavaScript wrapper function for each
#[wasm_bindgen]export inwasm/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
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
cargo install wasm-pack
Or via the installer script:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
Verify:
wasm-pack --version
# wasm-pack 0.13.x
3. Build the WASM module
From the project root:
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
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 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:
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:
cargo build -p server
How heartbeat.js Loads the Module
heartbeat.js uses a standard ES module dynamic import pattern:
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:
types {
application/wasm wasm;
}
Apache .htaccess:
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:
rustup target add wasm32-unknown-unknown
wasm-pack not found
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.
# 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:
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().