leviathan-crypto 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +265 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/SECURITY.md +174 -0
- package/dist/chacha.wasm +0 -0
- package/dist/chacha20/index.d.ts +49 -0
- package/dist/chacha20/index.js +177 -0
- package/dist/chacha20/ops.d.ts +16 -0
- package/dist/chacha20/ops.js +146 -0
- package/dist/chacha20/pool.d.ts +52 -0
- package/dist/chacha20/pool.js +188 -0
- package/dist/chacha20/pool.worker.d.ts +1 -0
- package/dist/chacha20/pool.worker.js +37 -0
- package/dist/chacha20/types.d.ts +30 -0
- package/dist/chacha20/types.js +1 -0
- package/dist/docs/architecture.md +795 -0
- package/dist/docs/argon2id.md +290 -0
- package/dist/docs/chacha20.md +602 -0
- package/dist/docs/chacha20_pool.md +306 -0
- package/dist/docs/fortuna.md +322 -0
- package/dist/docs/init.md +308 -0
- package/dist/docs/loader.md +206 -0
- package/dist/docs/serpent.md +914 -0
- package/dist/docs/sha2.md +620 -0
- package/dist/docs/sha3.md +509 -0
- package/dist/docs/types.md +198 -0
- package/dist/docs/utils.md +273 -0
- package/dist/docs/wasm.md +193 -0
- package/dist/embedded/chacha.d.ts +1 -0
- package/dist/embedded/chacha.js +2 -0
- package/dist/embedded/serpent.d.ts +1 -0
- package/dist/embedded/serpent.js +2 -0
- package/dist/embedded/sha2.d.ts +1 -0
- package/dist/embedded/sha2.js +2 -0
- package/dist/embedded/sha3.d.ts +1 -0
- package/dist/embedded/sha3.js +2 -0
- package/dist/fortuna.d.ts +72 -0
- package/dist/fortuna.js +445 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +44 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.js +49 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +30 -0
- package/dist/serpent/index.d.ts +65 -0
- package/dist/serpent/index.js +242 -0
- package/dist/serpent/seal.d.ts +8 -0
- package/dist/serpent/seal.js +70 -0
- package/dist/serpent/stream-encoder.d.ts +20 -0
- package/dist/serpent/stream-encoder.js +167 -0
- package/dist/serpent/stream-pool.d.ts +48 -0
- package/dist/serpent/stream-pool.js +285 -0
- package/dist/serpent/stream-sealer.d.ts +34 -0
- package/dist/serpent/stream-sealer.js +223 -0
- package/dist/serpent/stream.d.ts +28 -0
- package/dist/serpent/stream.js +205 -0
- package/dist/serpent/stream.worker.d.ts +32 -0
- package/dist/serpent/stream.worker.js +117 -0
- package/dist/serpent/types.d.ts +5 -0
- package/dist/serpent/types.js +1 -0
- package/dist/serpent.wasm +0 -0
- package/dist/sha2/hkdf.d.ts +16 -0
- package/dist/sha2/hkdf.js +108 -0
- package/dist/sha2/index.d.ts +40 -0
- package/dist/sha2/index.js +190 -0
- package/dist/sha2/types.d.ts +5 -0
- package/dist/sha2/types.js +1 -0
- package/dist/sha2.wasm +0 -0
- package/dist/sha3/index.d.ts +55 -0
- package/dist/sha3/index.js +246 -0
- package/dist/sha3/types.d.ts +5 -0
- package/dist/sha3/types.js +1 -0
- package/dist/sha3.wasm +0 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.js +26 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.js +169 -0
- package/package.json +90 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Module Initialization and WASM Loading
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> The `init()` function is the entry point for leviathan-crypto. You must call it
|
|
5
|
+
> before using any cryptographic class. It loads the WebAssembly modules that
|
|
6
|
+
> perform the actual cryptographic work, caches them in memory, and makes them
|
|
7
|
+
> available to all wrapper classes.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
leviathan-crypto runs all cryptographic computation inside WebAssembly modules.
|
|
12
|
+
These modules are not available automatically, they need to be loaded and
|
|
13
|
+
compiled before any cryptographic class (`Serpent`, `SHA256`, `ChaCha20`, etc.)
|
|
14
|
+
can be used.
|
|
15
|
+
|
|
16
|
+
`init()` handles this process for you. You tell it which modules you need, and
|
|
17
|
+
it loads them. After that, every class backed by those modules is ready to use.
|
|
18
|
+
|
|
19
|
+
Key properties:
|
|
20
|
+
|
|
21
|
+
- **Required before use.** If you try to create a cryptographic class before
|
|
22
|
+
calling `init()`, you will get a clear error telling you what to do.
|
|
23
|
+
- **Three loading modes.** Embedded (default, zero-config), streaming
|
|
24
|
+
(better performance for large apps), and manual (full control over how
|
|
25
|
+
binaries are provided).
|
|
26
|
+
- **Idempotent.** Calling `init()` multiple times with the same module is
|
|
27
|
+
safe, it skips modules that are already loaded. This means you can call
|
|
28
|
+
`init()` in multiple places in your application without worrying about
|
|
29
|
+
redundant work.
|
|
30
|
+
- **Async.** `init()` returns a Promise. Use `await` or `.then()` before
|
|
31
|
+
proceeding.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Security Notes
|
|
36
|
+
|
|
37
|
+
- **WASM runs outside the JavaScript JIT.** Cryptographic code executes in
|
|
38
|
+
WebAssembly, which provides more predictable execution timing compared to
|
|
39
|
+
optimized JavaScript. This reduces the risk of timing side-channels
|
|
40
|
+
introduced by the JIT compiler.
|
|
41
|
+
- **Independent memory per module.** Each WASM module gets its own linear
|
|
42
|
+
memory (3 pages, 192 KB). Key material loaded into one module's memory
|
|
43
|
+
cannot be read by another module. There is no shared memory between modules.
|
|
44
|
+
- **No silent auto-initialization.** Every wrapper class checks that its
|
|
45
|
+
backing module has been initialized. If it hasn't, the class throws an
|
|
46
|
+
error immediately rather than silently loading the module in the
|
|
47
|
+
background. This makes initialization explicit and auditable.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### Types
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type Module = 'serpent' | 'chacha20' | 'sha2' | 'sha3'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The four WASM module families. Each one backs a group of related classes:
|
|
60
|
+
|
|
61
|
+
| Module | Classes it enables |
|
|
62
|
+
|-------------|----------------------------------------------------------------|
|
|
63
|
+
| `'serpent'` | `Serpent`, `SerpentCbc`, `SerpentCtr`, `SerpentSeal`, `SerpentStream`, `SerpentStreamPool`, `Fortuna` |
|
|
64
|
+
| `'chacha20'`| `ChaCha20`, `XChaCha20Poly1305` |
|
|
65
|
+
| `'sha2'` | `SHA256`, `SHA384`, `SHA512`, `HMAC` (SHA-2 based), `Fortuna` |
|
|
66
|
+
| `'sha3'` | `SHA3`, `SHAKE128`, `SHAKE256` |
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
type Mode = 'embedded' | 'streaming' | 'manual'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
How the WASM binary is loaded. See the [Usage Examples](#usage-examples) section
|
|
73
|
+
for when to use each mode.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface InitOpts {
|
|
77
|
+
wasmUrl?: URL | string
|
|
78
|
+
wasmBinary?: Partial<Record<Module, Uint8Array | ArrayBuffer>>
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Optional configuration object. Which fields are required depends on the mode:
|
|
83
|
+
|
|
84
|
+
| Mode | Required fields | Description |
|
|
85
|
+
|-------------|--------------------|-------------------------------------------------|
|
|
86
|
+
| `embedded` | (none) | Binaries are bundled in the package |
|
|
87
|
+
| `streaming` | `wasmUrl` | Base URL where `.wasm` files are served |
|
|
88
|
+
| `manual` | `wasmBinary` | A map of module names to raw WASM binary data |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Functions
|
|
93
|
+
|
|
94
|
+
#### `init(modules, mode?, opts?)` — public API (exported from root barrel)
|
|
95
|
+
|
|
96
|
+
> [!NOTE]
|
|
97
|
+
> `init()` is no longer exported from `init.ts`. It is defined in the
|
|
98
|
+
> root barrel (`src/ts/index.ts`) and dispatches to each module's own init
|
|
99
|
+
> function (`serpentInit`, `chacha20Init`, `sha2Init`, `sha3Init`).
|
|
100
|
+
> See [README.md](./README.md) for details.
|
|
101
|
+
|
|
102
|
+
The public `init()` signature is unchanged:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
async function init(
|
|
106
|
+
modules: Module | Module[],
|
|
107
|
+
mode?: Mode, // default: 'embedded'
|
|
108
|
+
opts?: InitOpts,
|
|
109
|
+
): Promise<void>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
#### `initModule(mod, embeddedThunk, mode?, opts?)` — internal
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
async function initModule(
|
|
118
|
+
mod: Module,
|
|
119
|
+
embeddedThunk: () => Promise<string>,
|
|
120
|
+
mode?: Mode, // default: 'embedded'
|
|
121
|
+
opts?: InitOpts,
|
|
122
|
+
): Promise<void>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Internal initialization function. Called by each module's own init function
|
|
126
|
+
(`serpentInit`, `chacha20Init`, `sha2Init`, `sha3Init`), not by consumers
|
|
127
|
+
directly. Each module passes its own embedded thunk so the
|
|
128
|
+
dependency graph stays isolated per module, enabling tree-shaking.
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
|
|
132
|
+
- `mod`: The module name to initialize.
|
|
133
|
+
- `embeddedThunk`: A function that returns a Promise resolving to the
|
|
134
|
+
base64-encoded WASM binary. Each module defines its own thunk pointing to
|
|
135
|
+
its own embedded file.
|
|
136
|
+
- `mode`: The loading strategy. Defaults to `'embedded'`.
|
|
137
|
+
- `opts`: Configuration for `'streaming'` and `'manual'` modes.
|
|
138
|
+
|
|
139
|
+
**Returns:** A Promise that resolves when the module is loaded and cached.
|
|
140
|
+
|
|
141
|
+
**Idempotent:** If the module is already initialized, returns immediately.
|
|
142
|
+
|
|
143
|
+
**Throws:**
|
|
144
|
+
|
|
145
|
+
- `'leviathan-crypto: streaming mode requires wasmUrl'` if mode is
|
|
146
|
+
`'streaming'` and `opts.wasmUrl` is not provided.
|
|
147
|
+
- `'leviathan-crypto: manual mode requires wasmBinary['<mod>']'` if mode
|
|
148
|
+
is `'manual'` and the binary for the requested module is missing.
|
|
149
|
+
- `'leviathan-crypto: unknown mode '<mode>''` if an invalid mode string
|
|
150
|
+
is passed.
|
|
151
|
+
|
|
152
|
+
> [!NOTE]
|
|
153
|
+
> The previous design exported `init()` from `init.ts`,
|
|
154
|
+
> which contained a central `embeddedLoaders` record mapping every module name
|
|
155
|
+
> to its embedded import. This meant any consumer importing `init()`,
|
|
156
|
+
> even through a subpath like `leviathan-crypto/serpent`, pulled all four
|
|
157
|
+
> embedded binaries into the bundle. Moving `init()` to the root barrel and
|
|
158
|
+
> giving each module its own thunk isolates the dependency graph so bundlers
|
|
159
|
+
> can tree-shake unused modules, optimizing build size.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
#### `getInstance(mod)`
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
function getInstance(mod: Module): WebAssembly.Instance
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Returns the cached WebAssembly instance for a module. This is used internally
|
|
170
|
+
by wrapper classes, you do not normally need to call it yourself.
|
|
171
|
+
|
|
172
|
+
**Throws:**
|
|
173
|
+
`'leviathan-crypto: call init(['<mod>']) before using this class'` if the
|
|
174
|
+
module has not been initialized.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
#### `isInitialized(mod)`
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
function isInitialized(mod: Module): boolean
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Returns `true` if the given module has been loaded and cached (read-only).
|
|
185
|
+
Exported from both `init.ts` and the root barrel (`src/ts/index.ts`).
|
|
186
|
+
|
|
187
|
+
> [!NOTE]
|
|
188
|
+
> `isInitialized` is a diagnostic indicator only — not a control mechanism.
|
|
189
|
+
> Use `init()` to initialize modules; do not guard calls on this value.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
#### `_resetForTesting()`
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
function _resetForTesting(): void
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Clears all cached WASM instances. This is a testing utility, it allows test
|
|
200
|
+
suites to reset the initialization state between test runs. Do not use this in
|
|
201
|
+
production code.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Usage Examples
|
|
206
|
+
|
|
207
|
+
### Embedded mode (default: simplest)
|
|
208
|
+
|
|
209
|
+
This is the recommended mode for most applications. The WASM binaries are
|
|
210
|
+
bundled inside the package as base64-encoded strings, so there are no extra
|
|
211
|
+
files to serve or fetch.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { init, SHA256 } from 'leviathan-crypto'
|
|
215
|
+
|
|
216
|
+
await init('sha2')
|
|
217
|
+
|
|
218
|
+
const hash = new SHA256()
|
|
219
|
+
const digest = hash.hash(myData)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Initializing multiple modules at once
|
|
223
|
+
|
|
224
|
+
Pass an array to load several modules in a single call:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { init, Serpent, SHA256 } from 'leviathan-crypto'
|
|
228
|
+
|
|
229
|
+
await init(['serpent', 'sha2'])
|
|
230
|
+
|
|
231
|
+
const cipher = new Serpent(key)
|
|
232
|
+
const hash = new SHA256()
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Streaming mode (better performance for large apps)
|
|
236
|
+
|
|
237
|
+
Streaming mode fetches `.wasm` files from a URL and uses the browser's
|
|
238
|
+
streaming compilation (`WebAssembly.instantiateStreaming`). This can be
|
|
239
|
+
faster than embedded mode because the browser can begin compiling the WASM
|
|
240
|
+
binary while it is still downloading.
|
|
241
|
+
|
|
242
|
+
You must serve the `.wasm` files from your web server and provide the base
|
|
243
|
+
URL where they are hosted. The files must be served with the
|
|
244
|
+
`Content-Type: application/wasm` header.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { init, Serpent } from 'leviathan-crypto'
|
|
248
|
+
|
|
249
|
+
await init('serpent', 'streaming', {
|
|
250
|
+
wasmUrl: '/static/wasm/'
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const cipher = new Serpent(key)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The library knows the filename for each module (e.g. `serpent.wasm`,
|
|
257
|
+
`sha2.wasm`). You only need to provide the directory URL.
|
|
258
|
+
|
|
259
|
+
### Manual mode (full control)
|
|
260
|
+
|
|
261
|
+
Manual mode lets you provide the raw WASM binary yourself. This is useful if
|
|
262
|
+
you have a custom build pipeline, want to load binaries from a non-standard
|
|
263
|
+
source, or need to verify the binary before passing it to the library.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { init, SHA256 } from 'leviathan-crypto'
|
|
267
|
+
|
|
268
|
+
// Load the binary however you like
|
|
269
|
+
const wasmBinary = await fetch('/my-custom-path/sha2.wasm')
|
|
270
|
+
.then(r => r.arrayBuffer())
|
|
271
|
+
|
|
272
|
+
await init('sha2', 'manual', {
|
|
273
|
+
wasmBinary: { sha2: new Uint8Array(wasmBinary) }
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const hash = new SHA256()
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Checking if a module is initialized
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { isInitialized } from 'leviathan-crypto'
|
|
283
|
+
|
|
284
|
+
if (!isInitialized('sha2')) {
|
|
285
|
+
await init('sha2')
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Error Conditions
|
|
292
|
+
|
|
293
|
+
| Situation | What happens |
|
|
294
|
+
|-------------------------------------------|----------------------------------------------------------------------------------------------|
|
|
295
|
+
| Using a class before calling `init()` | Throws: `"leviathan-crypto: call init(['<mod>']) before using this class"` |
|
|
296
|
+
| Streaming mode without `wasmUrl` | Throws: `"leviathan-crypto: streaming mode requires wasmUrl"` |
|
|
297
|
+
| Manual mode without the needed binary | Throws: `"leviathan-crypto: manual mode requires wasmBinary['<mod>']"` |
|
|
298
|
+
| Unknown mode string | Throws: `"leviathan-crypto: unknown mode '<mode>'"` |
|
|
299
|
+
| Calling `init()` for an already-loaded module | No error. Module is silently skipped (idempotent behavior) |
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Cross-References
|
|
304
|
+
|
|
305
|
+
- [README.md](./README.md) — package overview and quick-start guide
|
|
306
|
+
- [loader.md](./loader.md) — WASM binary loading strategies (internal details)
|
|
307
|
+
- [architecture.md](./architecture.md) — architecture overview, module structure, and build pipeline
|
|
308
|
+
- [wasm.md](./wasm.md) — WebAssembly primer: modules, instances, memory, and the init gate
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# WASM Binary Loading Strategies
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> Internal module used by `init()` that handles the actual loading and
|
|
5
|
+
> instantiation of WebAssembly binaries. You normally do not interact
|
|
6
|
+
> with this module directly.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
When you call `init()`, it delegates the work of obtaining and compiling the
|
|
11
|
+
WASM binary to the loader. The loader supports three strategies:
|
|
12
|
+
|
|
13
|
+
- **Embedded** -- The WASM binary is already bundled in the package as a
|
|
14
|
+
base64-encoded string. The loader decodes it and instantiates the module.
|
|
15
|
+
No network requests are made. This is the default and simplest option.
|
|
16
|
+
- **Streaming** -- The loader fetches the `.wasm` file from a URL you provide
|
|
17
|
+
and uses the browser's streaming compilation API. The browser can start
|
|
18
|
+
compiling the binary while it is still downloading, which can improve
|
|
19
|
+
load times for larger modules.
|
|
20
|
+
- **Manual** -- You provide the raw binary data (as a `Uint8Array` or
|
|
21
|
+
`ArrayBuffer`) and the loader instantiates it directly. This gives you
|
|
22
|
+
full control over how the binary is obtained.
|
|
23
|
+
|
|
24
|
+
All three strategies produce the same result: a `WebAssembly.Instance` that
|
|
25
|
+
the wrapper classes use to perform cryptographic operations.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Security Notes
|
|
30
|
+
|
|
31
|
+
- **Embedded mode requires no network access.** The WASM binary is part of
|
|
32
|
+
the installed package. This eliminates the risk of a compromised CDN or
|
|
33
|
+
man-in-the-middle attack altering the binary at load time.
|
|
34
|
+
- **Streaming mode requires correct MIME type.** The `.wasm` files must be
|
|
35
|
+
served with `Content-Type: application/wasm`. This is a browser requirement
|
|
36
|
+
for `WebAssembly.instantiateStreaming`. If the header is missing or wrong,
|
|
37
|
+
the browser will reject the response.
|
|
38
|
+
- **Manual mode places integrity responsibility on you.** The loader
|
|
39
|
+
instantiates whatever binary you provide. If you use manual mode, you are
|
|
40
|
+
responsible for verifying that the binary is authentic and unmodified.
|
|
41
|
+
- **Each module gets its own memory.** Every instantiation creates a fresh
|
|
42
|
+
`WebAssembly.Memory` with 3 pages (192 KB). Modules cannot share or
|
|
43
|
+
access each other's memory. This means key material loaded into one
|
|
44
|
+
module's memory space is isolated from all other modules.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## API Reference
|
|
49
|
+
|
|
50
|
+
These functions are exported from `loader.ts` and called by `init.ts`. They
|
|
51
|
+
are not part of the public API -- they are documented here for completeness
|
|
52
|
+
and for contributors working on the internals.
|
|
53
|
+
|
|
54
|
+
### `loadEmbedded(thunk)`
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
async function loadEmbedded(
|
|
58
|
+
thunk: () => Promise<string>,
|
|
59
|
+
): Promise<WebAssembly.Instance>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Loads a WASM module from an embedded base64-encoded string obtained by calling
|
|
63
|
+
the provided thunk.
|
|
64
|
+
|
|
65
|
+
**How it works:**
|
|
66
|
+
|
|
67
|
+
1. Calls the thunk, which dynamically imports the embedded binary file and
|
|
68
|
+
returns the base64-encoded WASM string.
|
|
69
|
+
2. Decodes the base64 string to raw bytes.
|
|
70
|
+
3. Instantiates the WASM module with a fresh 3-page `WebAssembly.Memory`.
|
|
71
|
+
|
|
72
|
+
The thunk is provided by each module's own `init()` function (e.g.
|
|
73
|
+
`serpent/index.ts` passes `() => import('../embedded/serpent.js').then(m => m.WASM_BASE64)`).
|
|
74
|
+
This design means `loader.ts` has no knowledge of module names or embedded file
|
|
75
|
+
paths -- each module owns its own embedded import, enabling tree-shaking.
|
|
76
|
+
|
|
77
|
+
**Parameters:**
|
|
78
|
+
|
|
79
|
+
- `thunk` -- A function that returns a Promise resolving to a base64-encoded
|
|
80
|
+
WASM binary string. Each module's `index.ts` defines its own thunk pointing
|
|
81
|
+
to its own embedded file.
|
|
82
|
+
|
|
83
|
+
**Returns:** A Promise that resolves to a `WebAssembly.Instance`.
|
|
84
|
+
|
|
85
|
+
**Error conditions:**
|
|
86
|
+
|
|
87
|
+
- If the embedded binary file does not exist, this means the build step
|
|
88
|
+
(`scripts/embed-wasm.ts`) has not been run. Run `npm run build` to
|
|
89
|
+
generate the embedded files.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `loadStreaming(mod, baseUrl, filename)`
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
async function loadStreaming(
|
|
97
|
+
_mod: Module,
|
|
98
|
+
baseUrl: URL | string,
|
|
99
|
+
filename: string,
|
|
100
|
+
): Promise<WebAssembly.Instance>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Loads a WASM module by fetching it from a URL and using streaming compilation.
|
|
104
|
+
|
|
105
|
+
**How it works:**
|
|
106
|
+
|
|
107
|
+
1. Constructs the full URL by combining `baseUrl` and `filename`
|
|
108
|
+
(e.g. `https://example.com/wasm/` + `serpent.wasm`).
|
|
109
|
+
2. Calls `WebAssembly.instantiateStreaming(fetch(url), imports)`, which
|
|
110
|
+
allows the browser to compile the module while it downloads.
|
|
111
|
+
3. Creates a fresh 3-page `WebAssembly.Memory` for the instance.
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
|
|
115
|
+
- `_mod` -- The module name (currently unused in the function body, but
|
|
116
|
+
passed for consistency).
|
|
117
|
+
- `baseUrl` -- The base URL where `.wasm` files are hosted. Can be a
|
|
118
|
+
`URL` object or a string.
|
|
119
|
+
- `filename` -- The `.wasm` filename (e.g. `'serpent.wasm'`). This is
|
|
120
|
+
determined by `init.ts` using its internal filename mapping.
|
|
121
|
+
|
|
122
|
+
**Returns:** A Promise that resolves to a `WebAssembly.Instance`.
|
|
123
|
+
|
|
124
|
+
**Error conditions:**
|
|
125
|
+
|
|
126
|
+
- Network failure (server unreachable, 404, etc.) will cause the Promise
|
|
127
|
+
to reject.
|
|
128
|
+
- If the server does not respond with `Content-Type: application/wasm`,
|
|
129
|
+
the browser will reject the streaming compilation. This is a common
|
|
130
|
+
issue with misconfigured web servers -- ensure your server is configured
|
|
131
|
+
to serve `.wasm` files with the correct MIME type.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### `loadManual(binary)`
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
async function loadManual(
|
|
139
|
+
binary: Uint8Array | ArrayBuffer,
|
|
140
|
+
): Promise<WebAssembly.Instance>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Loads a WASM module from a raw binary you provide directly.
|
|
144
|
+
|
|
145
|
+
**How it works:**
|
|
146
|
+
|
|
147
|
+
1. Converts `ArrayBuffer` to `Uint8Array` if needed.
|
|
148
|
+
2. Instantiates the WASM module with a fresh 3-page `WebAssembly.Memory`.
|
|
149
|
+
|
|
150
|
+
**Parameters:**
|
|
151
|
+
|
|
152
|
+
- `binary` -- The raw WASM binary as a `Uint8Array` or `ArrayBuffer`.
|
|
153
|
+
|
|
154
|
+
**Returns:** A Promise that resolves to a `WebAssembly.Instance`.
|
|
155
|
+
|
|
156
|
+
**Error conditions:**
|
|
157
|
+
|
|
158
|
+
- If the binary is not a valid WASM module, `WebAssembly.instantiate` will
|
|
159
|
+
throw. The error message will come from the browser's WASM engine and
|
|
160
|
+
will typically mention a validation or compilation failure.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Internal Details
|
|
165
|
+
|
|
166
|
+
### Embedded binary ownership
|
|
167
|
+
|
|
168
|
+
Each module's `index.ts` owns the dynamic import to its own embedded binary
|
|
169
|
+
file. The loader has no knowledge of module names or file paths -- it receives
|
|
170
|
+
a thunk from `initModule()` and calls it. This means `loader.ts` has no
|
|
171
|
+
dependency on any embedded file, which enables bundlers to tree-shake unused
|
|
172
|
+
modules.
|
|
173
|
+
|
|
174
|
+
| Module | Thunk defined in | Embedded file path |
|
|
175
|
+
|-------------|-------------------------------|----------------------------|
|
|
176
|
+
| `serpent` | `serpent/index.ts` | `./embedded/serpent.js` |
|
|
177
|
+
| `chacha20` | `chacha20/index.ts` | `./embedded/chacha.js` |
|
|
178
|
+
| `sha2` | `sha2/index.ts` | `./embedded/sha2.js` |
|
|
179
|
+
| `sha3` | `sha3/index.ts` | `./embedded/sha3.js` |
|
|
180
|
+
|
|
181
|
+
The embedded `.js` files are generated by the build script
|
|
182
|
+
(`scripts/embed-wasm.ts`) and are gitignored. They are not meant to be
|
|
183
|
+
created or edited by hand.
|
|
184
|
+
|
|
185
|
+
### Base64 decoding
|
|
186
|
+
|
|
187
|
+
The loader handles base64 decoding in both browser and Node.js environments:
|
|
188
|
+
|
|
189
|
+
- **Browser:** Uses the built-in `atob()` function.
|
|
190
|
+
- **Node.js:** Falls back to `Buffer.from(b64, 'base64')`.
|
|
191
|
+
|
|
192
|
+
### Memory allocation
|
|
193
|
+
|
|
194
|
+
Every WASM instance receives a `WebAssembly.Memory` with exactly 3 pages
|
|
195
|
+
(192 KB total). The memory size is fixed -- modules do not grow their memory
|
|
196
|
+
at runtime. This is a deliberate design choice: fixed memory prevents
|
|
197
|
+
unexpected allocations and makes the memory layout predictable and auditable.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Cross-References
|
|
202
|
+
|
|
203
|
+
- [README.md](./README.md) — package overview and quick-start guide
|
|
204
|
+
- [architecture.md](./architecture.md) — architecture overview and build pipeline
|
|
205
|
+
- [init.md](./init.md) — the public `init()` API that uses this loader
|
|
206
|
+
- [wasm.md](./wasm.md) — WebAssembly primer: modules, instances, and memory model
|