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:
1 parent
2840ddfc58
commit
256f12023e
1 file changed
+301
@@ -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()`.
|
||||||
Reference in new issue
Block a user