WASM Platform Guide
This guide covers the installation, setup, and usage of MetaMUI Crypto Primitives compiled to WebAssembly (WASM) for use in browsers and Node.js.
Installation
NPM/Yarn
# npm
npm install @metamui/crypto-wasm
# yarn
yarn add @metamui/crypto-wasm
# pnpm
pnpm add @metamui/crypto-wasm
CDN
<!-- Latest version -->
<script src="https://unpkg.com/@metamui/crypto-wasm/dist/metamui-crypto.js"></script>
<!-- Specific version -->
<script src="https://unpkg.com/@metamui/crypto-wasm@0.1.0/dist/metamui-crypto.js"></script>
Quick Start
Browser (ES Modules)
import init, { Ed25519, ChaCha20Poly1305 } from '@metamui/crypto-wasm';
async function main() {
// Initialize WASM module
await init();
// Generate Ed25519 keypair
const keypair = Ed25519.generateKeypair();
// Sign a message
const message = new TextEncoder().encode('Hello, MetaMUI!');
const signature = keypair.sign(message);
// Verify signature
const isValid = Ed25519.verify(signature, message, keypair.publicKey);
console.log('Signature valid:', isValid);
// Encrypt with ChaCha20-Poly1305
const key = ChaCha20Poly1305.generateKey();
const cipher = new ChaCha20Poly1305(key);
const plaintext = new TextEncoder().encode('Secret message');
const nonce = ChaCha20Poly1305.generateNonce();
const encrypted = cipher.encrypt(plaintext, nonce);
console.log('Encrypted:', encrypted);
// Decrypt
const decrypted = cipher.decrypt(encrypted.ciphertext, encrypted.tag, nonce);
console.log('Decrypted:', new TextDecoder().decode(decrypted));
}
main().catch(console.error);
Node.js
const { Ed25519, ChaCha20Poly1305, init } = require('@metamui/crypto-wasm');
async function main() {
// Initialize WASM module
await init();
// Use the same API as browser
const keypair = Ed25519.generateKeypair();
console.log('Public key:', Buffer.from(keypair.publicKey).toString('hex'));
}
main();
Bundler Configuration
Webpack 5
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true,
syncWebAssembly: true
},
module: {
rules: [
{
test: /\.wasm$/,
type: 'webassembly/async',
}
]
}
};
Vite
// vite.config.js
import { defineConfig } from 'vite';
import wasm from 'vite-plugin-wasm';
export default defineConfig({
plugins: [wasm()],
optimizeDeps: {
exclude: ['@metamui/crypto-wasm']
}
});
Rollup
// rollup.config.js
import { wasm } from '@rollup/plugin-wasm';
export default {
plugins: [
wasm({
targetEnv: 'auto-inline'
})
]
};
Advanced Usage
Streaming Initialization
import { instantiateStreaming } from '@metamui/crypto-wasm';
// For better performance with large WASM modules
async function initWithStreaming() {
const wasmUrl = new URL('@metamui/crypto-wasm/metamui_crypto_bg.wasm', import.meta.url);
const response = await fetch(wasmUrl);
const { Ed25519, ChaCha20Poly1305 } = await instantiateStreaming(response);
// Use the crypto functions
const keypair = Ed25519.generateKeypair();
}
Memory Management
import init, { Ed25519, Memory } from '@metamui/crypto-wasm';
async function efficientCrypto() {
await init();
// Pre-allocate memory for better performance
const memory = new Memory();
// Process large data efficiently
const largeData = new Uint8Array(1024 * 1024); // 1MB
// Use memory views to avoid copying
const view = memory.allocate(largeData.length);
view.set(largeData);
// Perform crypto operations on the view
const keypair = Ed25519.generateKeypair();
const signature = keypair.signView(view);
// Free memory when done
memory.free(view);
}
Web Workers
// crypto-worker.js
import init, { Ed25519, ChaCha20Poly1305 } from '@metamui/crypto-wasm';
let initialized = false;
self.addEventListener('message', async (event) => {
if (!initialized) {
await init();
initialized = true;
}
const { type, data } = event.data;
switch (type) {
case 'generateKeypair':
const keypair = Ed25519.generateKeypair();
self.postMessage({
type: 'keypair',
publicKey: keypair.publicKey,
privateKey: keypair.privateKey
});
break;
case 'encrypt':
const { plaintext, key, nonce } = data;
const cipher = new ChaCha20Poly1305(key);
const encrypted = cipher.encrypt(plaintext, nonce);
self.postMessage({
type: 'encrypted',
ciphertext: encrypted.ciphertext,
tag: encrypted.tag
});
break;
}
});
// main.js
const worker = new Worker('./crypto-worker.js', { type: 'module' });
worker.postMessage({ type: 'generateKeypair' });
worker.addEventListener('message', (event) => {
if (event.data.type === 'keypair') {
console.log('Generated keypair:', event.data);
}
});
React Integration
import React, { useState, useEffect } from 'react';
import init, { Ed25519, ChaCha20Poly1305 } from '@metamui/crypto-wasm';
function CryptoComponent() {
const [isReady, setIsReady] = useState(false);
const [keypair, setKeypair] = useState(null);
const [message, setMessage] = useState('');
const [signature, setSignature] = useState('');
useEffect(() => {
init().then(() => setIsReady(true));
}, []);
const generateKeypair = () => {
if (!isReady) return;
const kp = Ed25519.generateKeypair();
setKeypair(kp);
};
const signMessage = () => {
if (!keypair || !message) return;
const messageBytes = new TextEncoder().encode(message);
const sig = keypair.sign(messageBytes);
setSignature(Buffer.from(sig).toString('hex'));
};
if (!isReady) {
return <div>Loading WASM module...</div>;
}
return (
<div>
<button onClick={generateKeypair}>Generate Keypair</button>
{keypair && (
<>
<div>
Public Key: {Buffer.from(keypair.publicKey).toString('hex')}
</div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Enter message"
/>
<button onClick={signMessage}>Sign Message</button>
{signature && (
<div>Signature: {signature}</div>
)}
</>
)}
</div>
);
}
Vue.js Integration
<template>
<div>
<button @click="generateKeypair" :disabled="!isReady">
Generate Keypair
</button>
<div v-if="publicKey">
<p>Public Key: </p>
<input v-model="message" placeholder="Enter message" />
<button @click="signMessage">Sign</button>
<p v-if="signature">Signature: </p>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import init, { Ed25519 } from '@metamui/crypto-wasm';
export default {
setup() {
const isReady = ref(false);
const keypair = ref(null);
const publicKey = ref('');
const message = ref('');
const signature = ref('');
onMounted(async () => {
await init();
isReady.value = true;
});
const generateKeypair = () => {
keypair.value = Ed25519.generateKeypair();
publicKey.value = Buffer.from(keypair.value.publicKey).toString('hex');
};
const signMessage = () => {
if (!keypair.value || !message.value) return;
const messageBytes = new TextEncoder().encode(message.value);
const sig = keypair.value.sign(messageBytes);
signature.value = Buffer.from(sig).toString('hex');
};
return {
isReady,
publicKey,
message,
signature,
generateKeypair,
signMessage
};
}
};
</script>
Performance Optimization
SIMD Support
import init, { initSimd, hasSIMD } from '@metamui/crypto-wasm';
async function initWithOptimizations() {
// Check for SIMD support
if (await hasSIMD()) {
console.log('SIMD supported, using optimized version');
await initSimd();
} else {
console.log('SIMD not supported, using standard version');
await init();
}
}
Batch Operations
import { Ed25519Batch } from '@metamui/crypto-wasm';
async function batchVerification() {
const batch = new Ed25519Batch();
// Add signatures to verify
for (let i = 0; i < 100; i++) {
batch.add(signatures[i], messages[i], publicKeys[i]);
}
// Verify all at once (much faster than individual verification)
const allValid = await batch.verify();
console.log('All signatures valid:', allValid);
// Get individual results
const results = batch.getResults();
results.forEach((valid, index) => {
console.log(`Signature ${index}: ${valid ? 'valid' : 'invalid'}`);
});
}
Memory Views for Large Data
import { createView, ChaCha20Poly1305 } from '@metamui/crypto-wasm';
async function encryptLargeFile(file) {
const key = ChaCha20Poly1305.generateKey();
const cipher = new ChaCha20Poly1305(key);
const reader = file.stream().getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Create view without copying
const view = createView(value);
const nonce = ChaCha20Poly1305.generateNonce();
// Encrypt directly from view
const encrypted = cipher.encryptView(view, nonce);
chunks.push({
ciphertext: encrypted.ciphertext,
tag: encrypted.tag,
nonce: nonce
});
// View is automatically freed
}
return chunks;
}
Browser Compatibility
Feature Detection
async function checkCompatibility() {
const features = {
wasm: typeof WebAssembly !== 'undefined',
bigInt: typeof BigInt !== 'undefined',
textEncoder: typeof TextEncoder !== 'undefined',
crypto: typeof crypto !== 'undefined' && crypto.getRandomValues,
simd: WebAssembly.validate(new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x00, 0x03, 0x02,
0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00, 0xfd,
0x0c, 0x00, 0x00, 0x00, 0x00, 0x0b
]))
};
console.log('Browser features:', features);
if (!features.wasm) {
throw new Error('WebAssembly not supported');
}
return features;
}
Polyfills
// Polyfill for older browsers
if (!globalThis.crypto) {
globalThis.crypto = {
getRandomValues(array) {
for (let i = 0; i < array.length; i++) {
array[i] = Math.floor(Math.random() * 256);
}
return array;
}
};
}
// TextEncoder/TextDecoder polyfill
if (!globalThis.TextEncoder) {
const { TextEncoder, TextDecoder } = require('util');
globalThis.TextEncoder = TextEncoder;
globalThis.TextDecoder = TextDecoder;
}
Security Considerations
Content Security Policy
<!-- Allow WASM execution -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'">
Secure Random Generation
import { getRandomValues } from '@metamui/crypto-wasm';
// Use crypto.getRandomValues when available
function secureRandom(length) {
const array = new Uint8Array(length);
if (crypto && crypto.getRandomValues) {
crypto.getRandomValues(array);
} else {
// Fallback to WASM implementation
getRandomValues(array);
}
return array;
}
Memory Protection
import { SecureMemory } from '@metamui/crypto-wasm';
// Automatically zeroed on cleanup
class SecureKey {
constructor(keyData) {
this.memory = new SecureMemory(keyData.length);
this.memory.write(keyData);
}
use(callback) {
const data = this.memory.read();
try {
return callback(data);
} finally {
// Memory is automatically cleared
this.memory.clear();
}
}
}
Debugging
Enable Debug Mode
import init, { enableDebug } from '@metamui/crypto-wasm';
async function debugSetup() {
await init();
// Enable detailed logging
enableDebug(true);
// Now operations will log details
const keypair = Ed25519.generateKeypair();
// Logs: [DEBUG] Generating Ed25519 keypair...
// Logs: [DEBUG] Public key: 3b6a27bc...
}
Performance Profiling
import { performance } from '@metamui/crypto-wasm';
async function profileCrypto() {
performance.mark('start-keygen');
const keypair = Ed25519.generateKeypair();
performance.mark('end-keygen');
performance.mark('start-sign');
const signature = keypair.sign(new Uint8Array(1024));
performance.mark('end-sign');
performance.measure('keygen', 'start-keygen', 'end-keygen');
performance.measure('sign', 'start-sign', 'end-sign');
const measurements = performance.getEntriesByType('measure');
measurements.forEach(measure => {
console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);
});
}
Common Issues
1. WASM MIME Type
# nginx.conf
location ~ \.wasm$ {
add_header Content-Type application/wasm;
}
2. CORS Issues
// Serve WASM with proper CORS headers
app.use((req, res, next) => {
if (req.path.endsWith('.wasm')) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/wasm');
}
next();
});
3. Module Loading
// Handle different environments
async function loadWASM() {
if (typeof window !== 'undefined') {
// Browser
return import('@metamui/crypto-wasm');
} else if (typeof global !== 'undefined') {
// Node.js
const { readFileSync } = require('fs');
const { join } = require('path');
const wasmPath = join(__dirname, 'node_modules/@metamui/crypto-wasm/metamui_crypto_bg.wasm');
const wasmBuffer = readFileSync(wasmPath);
return WebAssembly.instantiate(wasmBuffer);
}
}
Build Configuration
Custom Build
# Clone the repository
git clone https://github.com/metamui/crypto-wasm
cd crypto-wasm
# Install dependencies
npm install
# Build with custom features
npm run build -- --features "ed25519,chacha20" --no-default-features
# Build with SIMD support
npm run build -- --features simd
# Build for specific target
npm run build -- --target web
npm run build -- --target nodejs
npm run build -- --target bundler
Size Optimization
// webpack.config.js for minimal bundle
module.exports = {
optimization: {
usedExports: true,
sideEffects: false
},
plugins: [
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, '.'),
extraArgs: '--no-typescript --weak-refs --reference-types',
forceMode: 'production'
})
]
};