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.

Note: The WASM build uses non-NIST encoding (896-byte public keys, 666-byte fixed signatures). Cryptographic correctness has been verified via the Rust backend.

Installation

Build from source:

cd metamui-crypto-typescript/packages/falcon
npm install
npm run build

Running Tests

cd metamui-crypto-typescript/packages/falcon
npx vitest run

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

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;
}

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();
        }
    }
}

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

Resources