FlatBuffers WASM Runtime Integration
Run the FlatBuffers encryption module in any language with WebAssembly support. The same cryptographic implementation works across all platforms, ensuring consistent behavior and cross-language interoperability.
Why WASM Runtimes?
Single Auditable Implementation
The encryption module is compiled from a single C++ codebase to WebAssembly. This means:
- One codebase to audit - Security teams can focus on a single implementation
- Consistent behavior - Identical cryptographic operations across all languages
- No native dependencies - Pure WASM runs in any compliant runtime
Battle-Tested Cryptography
Two cryptographic backends are available. The pre-built WASM module uses Crypto++ by default (configured via FLATBUFFERS_USE_CRYPTOPP=1 in the CMake build).
Default: Crypto++ (Pre-built)
The default WASM build (flatc-encryption.wasm) uses Crypto++, a cryptographic library with:
- 30+ years of production use
- FIPS 140-2 validation
- Extensive peer review and security audits
- Active maintenance and security updates
- Full algorithm support including X25519, Ed25519, secp256k1
FIPS Support via hd-wallet-wasm
FIPS 140-3 compatible algorithms are available through hd-wallet-wasm, which includes
built-in OpenSSL FIPS support. Enable it with wallet.initFips():
- NIST-approved curves (P-256, P-384)
- AES-256-GCM authenticated encryption
- HKDF, PBKDF2, scrypt key derivation
- Full multi-curve support (secp256k1, Ed25519, X25519) also available in non-FIPS mode
Cross-Language Interoperability
Data encrypted in one language can be decrypted in any other:
Go ↔ Python ↔ Rust ↔ Java ↔ C# ↔ Swift ↔ Node.js ↔ Browser
All implementations use the same:
- Key formats (raw bytes)
- Nonce derivation (96-bit addition from
nonceStart) - Encryption algorithms (AES-256-CTR)
- Key derivation (HKDF-SHA256)
- Signature formats (DER-encoded ECDSA, raw Ed25519)
Nonce-Based Session Security
flatc-wasm uses a nonce incrementor to prevent nonce reuse attacks in AES-CTR mode:
- Session establishment: Sender generates random 12-byte
nonceStartvia CSPRNG - Header transmission:
EncryptionHeader(containingnonceStart) sent before encrypted data - Nonce derivation: Each field gets unique nonce via
nonceStart + (recordIndex × 65536 + fieldId) - Offline decryption: Recipients can decrypt any record in any order using just the header
This enables:
- Out-of-order decryption - Records can arrive or be processed in any sequence
- Parallel decryption - Multiple workers can decrypt different records simultaneously
- Offline operation - No connection to sender required after receiving header
- Index recovery - If record index is lost, try sequential indices until decryption succeeds
See Encryption Sessions & Nonce Management for detailed documentation.
Generated Code Encryption
All 12 FlatBuffers code generators now emit encryption support when your schema contains (encrypted) fields:
table UserRecord {
id: uint64;
name: string;
ssn: string (encrypted); // Encrypted at rest
credit_card: string (encrypted); // Encrypted at rest
}
Generated code automatically includes:
FlatbuffersEncryptionclass/module with AES-256-CTR implementationencryptionCtxfield on tables with encrypted fieldswithEncryption()factory for creating decryption-enabled readers- Transparent field decryption when encryption context is provided
| Language | Encryption Library |
|---|---|
| C++ | flatbuffers/encryption.h inline helpers |
| TypeScript | Pure TypeScript AES-256-CTR |
| Python | cryptography library |
| Go | crypto/aes + crypto/cipher |
| Rust | Pure Rust AES-256-CTR |
| Java | javax.crypto.Cipher |
| C# | System.Security.Cryptography |
| Swift | Pure Swift AES-256-CTR |
| Kotlin | javax.crypto.Cipher |
| PHP | openssl_encrypt/decrypt |
| Dart | pointycastle library |
| Lobster | Placeholder (no crypto library) |
Supported Runtimes
| Language | Runtime | Package | Key Features |
|---|---|---|---|
| Go | wazero | github.com/tetratelabs/wazero |
Pure Go, no CGo, zero dependencies |
| Python | wasmer | pip install wasmer |
PyPI ready, type hints |
| Rust | wasmer | wasmer crate |
no_std support, memory safe |
| Java | Chicory | Maven Central | Pure Java, no JNI |
| C# | Wasmtime | NuGet | .NET 6+, async support |
| Swift | WasmKit | Swift Package Manager | iOS/macOS, pure Swift |
| Node.js | V8 (native) | npm install flatc-wasm |
ESM/CJS, TypeScript |
| Browser | V8/SpiderMonkey/JSC | CDN or bundler | All modern browsers |
Cryptographic Operations
Symmetric Encryption
| Operation | Algorithm | Key Size | IV Size |
|---|---|---|---|
| Encryption/Decryption | AES-256-CTR | 32 bytes | 16 bytes |
Key Exchange (ECDH)
| Curve | Private Key | Public Key | Shared Secret | Use Case |
|---|---|---|---|---|
| X25519 | 32 bytes | 32 bytes | 32 bytes | General purpose, Signal, WireGuard |
| secp256k1 | 32 bytes | 33 bytes* | 32 bytes | Bitcoin, Ethereum, cryptocurrencies |
| P-256 | 32 bytes | 33 bytes* | 32 bytes | TLS, enterprise, NIST compliance |
*Compressed format. Uncompressed (65 bytes) also supported for input.
Digital Signatures
| Algorithm | Private Key | Public Key | Signature | Use Case |
|---|---|---|---|---|
| Ed25519 | 64 bytes | 32 bytes | 64 bytes | Fast, deterministic, Solana/Cardano |
| secp256k1 ECDSA | 32 bytes | 33 bytes | 70-72 bytes* | Bitcoin, Ethereum transactions |
| P-256 ECDSA | 32 bytes | 33 bytes | 70-72 bytes* | TLS, enterprise PKI |
*DER-encoded, variable length.
Key Derivation
| Function | Input | Output | Use Case |
|---|---|---|---|
| HKDF-SHA256 | IKM + salt + info | Variable length | Derive keys from ECDH shared secrets |
| SHA-256 | Any data | 32 bytes | Hashing, message digests |
API Reference
All runtimes expose the same WASM functions. Memory management follows the pattern:
- Allocate memory with
malloc - Write input data to allocated memory
- Call the cryptographic function
- Read output from memory
- Free allocated memory with
free
Core Functions
| Function | Signature | Description |
|---|---|---|
malloc |
(size: i32) -> i32 |
Allocate size bytes, returns pointer |
free |
(ptr: i32) |
Free memory at pointer |
Hash Functions
| Function | Signature | Description |
|---|---|---|
wasi_sha256 |
(data: i32, len: i32, out: i32) |
SHA-256 hash. out must be 32 bytes |
wasi_hkdf |
(ikm: i32, ikm_len: i32, salt: i32, salt_len: i32, info: i32, info_len: i32, out: i32, out_len: i32) |
HKDF-SHA256. salt can be NULL (0) |
Symmetric Encryption
| Function | Signature | Description |
|---|---|---|
wasi_encrypt_bytes |
(key: i32, iv: i32, data: i32, len: i32) -> i32 |
AES-256-CTR encrypt in-place. Returns 0 on success |
wasi_decrypt_bytes |
(key: i32, iv: i32, data: i32, len: i32) -> i32 |
AES-256-CTR decrypt in-place. Returns 0 on success |
X25519 Key Exchange
| Function | Signature | Description |
|---|---|---|
wasi_x25519_generate_keypair |
(priv_out: i32, pub_out: i32) -> i32 |
Generate keypair. priv_out: 32 bytes, pub_out: 32 bytes |
wasi_x25519_shared_secret |
(priv: i32, pub: i32, out: i32) -> i32 |
Compute shared secret. out: 32 bytes |
secp256k1 (Bitcoin/Ethereum)
| Function | Signature | Description |
|---|---|---|
wasi_secp256k1_generate_keypair |
(priv_out: i32, pub_out: i32) -> i32 |
Generate keypair. priv_out: 32 bytes, pub_out: 33 bytes |
wasi_secp256k1_shared_secret |
(priv: i32, pub: i32, pub_len: i32, out: i32) -> i32 |
ECDH. pub_len: 33 or 65 |
wasi_secp256k1_sign |
(priv: i32, data: i32, len: i32, sig_out: i32, sig_len_out: i32) -> i32 |
Sign (usually a hash). sig_out: max 72 bytes |
wasi_secp256k1_verify |
(pub: i32, pub_len: i32, data: i32, len: i32, sig: i32, sig_len: i32) -> i32 |
Verify. Returns 0 if valid |
P-256 (NIST)
| Function | Signature | Description |
|---|---|---|
wasi_p256_generate_keypair |
(priv_out: i32, pub_out: i32) -> i32 |
Generate keypair. priv_out: 32 bytes, pub_out: 33 bytes |
wasi_p256_shared_secret |
(priv: i32, pub: i32, pub_len: i32, out: i32) -> i32 |
ECDH. pub_len: 33 or 65 |
wasi_p256_sign |
(priv: i32, data: i32, len: i32, sig_out: i32, sig_len_out: i32) -> i32 |
Sign (usually a hash). sig_out: max 72 bytes |
wasi_p256_verify |
(pub: i32, pub_len: i32, data: i32, len: i32, sig: i32, sig_len: i32) -> i32 |
Verify. Returns 0 if valid |
Ed25519 Signatures
| Function | Signature | Description |
|---|---|---|
wasi_ed25519_generate_keypair |
(priv_out: i32, pub_out: i32) -> i32 |
Generate keypair. priv_out: 64 bytes, pub_out: 32 bytes |
wasi_ed25519_sign |
(priv: i32, data: i32, len: i32, sig_out: i32) -> i32 |
Sign message. sig_out: 64 bytes |
wasi_ed25519_verify |
(pub: i32, data: i32, len: i32, sig: i32) -> i32 |
Verify. Returns 0 if valid |
Security Considerations
Key Management
- Generate keys securely - Use the WASM module's key generation functions, which use the platform's cryptographic RNG
- Never reuse IVs - Generate a new random 16-byte IV for each encryption operation
- Derive keys with HKDF - Never use raw ECDH output directly as an encryption key
Shared Secret → HKDF(secret, salt, info) → Encryption Key
- Use unique info strings - Different purposes should use different HKDF info parameters:
HKDF(secret, null, "encryption-key-v1") → Key for encrypting
HKDF(secret, null, "authentication-key-v1") → Key for MAC (if needed)
Memory Handling
- Zero sensitive data - After use, overwrite key material in memory
- Minimize key lifetime - Keep keys in WASM memory only as long as needed
- Free allocated memory - Always call
free()to prevent memory leaks
What This Module Does NOT Protect
- Key storage - You must implement secure key storage for your platform
- Side-channel attacks - WASM runtimes may not be constant-time
- Authentication - Use signatures or HMAC to verify message integrity
- Forward secrecy - Implement ephemeral keys if needed
Performance Optimization
1. Cache the WASM Module
Loading and compiling WASM is expensive. Initialize once and reuse:
// Good: Initialize once
const module = await initEncryption();
// Reuse for all operations
// Bad: Initialize per operation
async function encrypt(data) {
const module = await initEncryption(); // Slow!
// ...
}
2. Batch Operations
Minimize WASM boundary crossings by processing multiple items:
// Good: Batch processing
const allData = items.map(i => i.data);
const results = encryptBatch(allData, key, ivs);
// Less efficient: One at a time
for (const item of items) {
encrypt(item.data, key, iv);
}
3. Reuse Memory Allocations
For repeated operations of the same size, reuse allocated buffers:
// Good: Reuse buffers
keyPtr, _ := malloc(32)
defer free(keyPtr)
for _, data := range items {
writeBytes(keyPtr, key)
encrypt(keyPtr, ivPtr, dataPtr, len)
}
// Less efficient: Allocate each time
for _, data := range items {
keyPtr, _ := malloc(32)
// ...
free(keyPtr)
}
4. Use Streaming for Large Data
For large files, process in chunks rather than loading everything into memory:
CHUNK_SIZE = 64 * 1024 # 64KB chunks
def encrypt_file(input_path, output_path, key):
iv = os.urandom(16)
with open(input_path, 'rb') as f_in, open(output_path, 'wb') as f_out:
f_out.write(iv) # Prepend IV
while chunk := f_in.read(CHUNK_SIZE):
encrypted = encrypt_bytes(key, iv, chunk)
f_out.write(encrypted)
# CTR mode: increment IV for next chunk
iv = increment_iv(iv)
Troubleshooting
Common Issues
"Cannot find module" / "WASM module not found"
The WASM binary must be accessible at runtime. Solutions:
- Bundle with your application - Copy
flatc-encryption.wasmto your build output - Use absolute paths - Specify the full path to the WASM file
- Check file permissions - Ensure the WASM file is readable
"Import not found: wasi_snapshot_preview1"
The WASM module requires WASI imports. Most runtimes provide these automatically:
- wazero (Go): Use
wasi_snapshot_preview1.MustInstantiate() - wasmer (Python): Configure WASI before instantiation
- Wasmtime (C#): Call
linker.DefineWasi()
"Import not found: env.invoke_*"
The module uses Emscripten's exception handling. You need to provide stub implementations:
// Minimal invoke_* stubs
const imports = {
env: {
invoke_v: (idx) => { /* call table[idx]() */ },
invoke_vi: (idx, a) => { /* call table[idx](a) */ },
invoke_vii: (idx, a, b) => { /* call table[idx](a, b) */ },
// ... etc
}
};
See language-specific guides for complete implementations.
"Memory access out of bounds"
You're reading or writing outside allocated memory. Check:
- Pointer validity - Ensure
mallocreturned non-zero - Buffer sizes - Don't write more bytes than allocated
- Double-free - Don't free the same pointer twice
"Invalid signature" / "Verification failed"
- Check key types - Ed25519 uses 64-byte private keys, ECDSA uses 32-byte
- Hash before signing - secp256k1 and P-256 expect a 32-byte hash, not raw message
- Public key format - Ensure compressed (33 bytes) vs uncompressed (65 bytes) matches
Examples
End-to-End Encryption
Complete example of encrypting a FlatBuffer for a recipient:
// 1. Generate sender's ephemeral keypair
const senderKeys = x25519GenerateKeyPair();
// 2. Compute shared secret with recipient's public key
const sharedSecret = x25519SharedSecret(
senderKeys.privateKey,
recipientPublicKey
);
// 3. Derive encryption key using HKDF
const encryptionKey = hkdf(
sharedSecret,
null, // no salt
new TextEncoder().encode('flatbuffer-encryption-v1'),
32 // 256 bits
);
// 4. Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(16));
// 5. Encrypt the FlatBuffer
const ciphertext = new Uint8Array(flatbuffer);
encryptBytes(ciphertext, encryptionKey, iv);
// 6. Package for transmission
const message = {
senderPublicKey: senderKeys.publicKey, // 32 bytes
iv: iv, // 16 bytes
ciphertext: ciphertext // encrypted data
};
Cross-Language Verification
Verify a signature created in another language:
// Go: Verify Ed25519 signature from Python
func verifyPythonSignature(publicKey, message, signature []byte) bool {
module := loadEncryptionModule()
pubPtr := writeBytes(module, publicKey)
msgPtr := writeBytes(module, message)
sigPtr := writeBytes(module, signature)
defer freeAll(module, pubPtr, msgPtr, sigPtr)
result := module.ed25519Verify(pubPtr, msgPtr, len(message), sigPtr)
return result == 0 // 0 = valid
}
Embedded Language Runtimes
flatc-wasm ships with the complete FlatBuffers runtime library source code for 11 languages, Brotli-compressed and embedded directly in the WASM binary. Retrieve runtime files as JSON file maps or ZIP archives — no network access or separate package installation required.
Available Runtimes
| Language | Key | Source Directory |
|---|---|---|
| Python | python |
python/flatbuffers/ |
| TypeScript | ts |
ts/ |
| Go | go |
go/ |
| Java | java |
java/.../com/google/flatbuffers/ |
| Kotlin | kotlin |
kotlin/.../src/ |
| Swift | swift |
swift/Sources/ |
| Dart | dart |
dart/lib/ |
| PHP | php |
php/ |
| C# | csharp |
net/FlatBuffers/ |
| C++ | cpp |
include/flatbuffers/ |
| Rust | rust |
rust/flatbuffers/src/ |
Retrieval
import { FlatcRunner } from 'flatc-wasm';
const flatc = await FlatcRunner.init();
// List available runtimes
const languages = flatc.listEmbeddedRuntimes();
// ["python", "ts", "go", "java", "kotlin", "swift", "dart", "php", "csharp", "cpp", "rust"]
// Get as JSON file map { "path": "content", ... }
const files = flatc.getEmbeddedRuntime('python');
// Get as downloadable ZIP archive
const zip = flatc.getEmbeddedRuntimeZip('go');
// Language aliases are supported:
// "typescript"/"js" → "ts", "c++" → "cpp", "c#"/"cs" → "csharp"
const tsFiles = flatc.getEmbeddedRuntime('typescript'); // same as 'ts'
Low-Level API
| Function | Description |
|---|---|
_wasm_list_embedded_runtimes(outSize) |
JSON array of language names |
_wasm_get_embedded_runtime_json(lang, outSize) |
Decompress → JSON file map |
_wasm_get_embedded_runtime_zip(lang, outSize) |
Decompress → ZIP archive |
_wasm_get_embedded_runtime_info(lang, fileCount, rawSize, compressedSize) |
Metadata (1=found, 0=not) |
How It Works
- At build time,
scripts/generate_embedded_runtimes.mjsreads runtime source files, creates JSON maps, Brotli-compresses each (quality 11), and emits a C header with static byte arrays - The header is compiled into the WASM binary (~264 KB compressed for all 11 languages)
- At runtime, Brotli decompression happens inside WASM — no JavaScript decompression needed
- ZIP archive construction also happens in WASM using a minimal Store-only ZIP builder
Language Guides
For detailed integration instructions, see:
- Streaming API - Producer/consumer patterns, encrypted streams, ring buffers
- Encryption Sessions - Nonce management, session establishment, offline decryption
- Go Integration - wazero runtime, pure Go
- Python Integration - wasmer with Cranelift
- Rust Integration - wasmer crate
- Java Integration - Chicory pure Java runtime
- C# Integration - Wasmtime .NET bindings
- Swift Integration - WasmKit for iOS/macOS
- Node.js Integration - Native V8 WASM support
- Browser Integration - All modern browsers