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'
        })
    ]
};

Resources