Files
nx9-chronoseal-rs/docs/WASM_BUILD.md
T
thakares 256f12023e 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)
2026-05-09 18:17:53 +05:30

8.0 KiB
Raw Blame History

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:

  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

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().