Multi-Language Integration
Voltaire combines four languages, each chosen for specific strengths.Language Roles
| Language | Role | Examples |
|---|---|---|
| TypeScript | Public API, type safety | Branded types, namespace exports |
| Zig | Core implementation, cross-compilation | Primitives, precompiles |
| Rust | Complex crypto (arkworks) | BLS12-381, BN254 curves |
| C | Vendored libraries | blst, c-kzg-4844 |
Architecture
Copy
Ask AI
┌─────────────────────────────────────────────────┐
│ TypeScript API │
│ (Branded Uint8Array, Namespace exports) │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Bun FFI │ │ WASM Loader │ │
│ │ (Native) │ │ (Browser) │ │
│ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │
├─────────┴─────────────────────────┴─────────────┤
│ Zig Core │
│ (primitives, crypto, precompiles) │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Rust │ │ C │ │
│ │ (arkworks) │ │ (blst, c-kzg) │ │
│ └─────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
Zig-to-TypeScript (Primary Path)
Native FFI (Bun)
Bun’s FFI provides near-native performance:Copy
Ask AI
// native/index.ts
import { dlopen, FFIType, suffix } from "bun:ffi";
const lib = dlopen(`libprimitives_ts_native.${suffix}`, {
keccak256: {
args: [FFIType.ptr, FFIType.u32, FFIType.ptr],
returns: FFIType.void,
},
});
export function keccak256(data: Uint8Array): Uint8Array {
const output = new Uint8Array(32);
lib.symbols.keccak256(data, data.length, output);
return output;
}
WASM (Browser/Node)
For non-Bun environments:Copy
Ask AI
// wasm/Keccak256.ts
import { getWasm } from "../wasm-loader/loader.js";
export function hash(data: Uint8Array): Uint8Array {
const wasm = getWasm();
const inputPtr = wasm.alloc(data.length);
const outputPtr = wasm.alloc(32);
try {
new Uint8Array(wasm.memory.buffer, inputPtr, data.length).set(data);
wasm.keccak256(inputPtr, data.length, outputPtr);
const result = new Uint8Array(32);
result.set(new Uint8Array(wasm.memory.buffer, outputPtr, 32));
return result;
} finally {
wasm.free(inputPtr);
wasm.free(outputPtr);
}
}
Exporting from Zig
Copy
Ask AI
// c_api.zig - exports for FFI/WASM
const std = @import("std");
const Keccak256 = @import("crypto").Keccak256;
export fn keccak256(input: [*]const u8, len: usize, output: [*]u8) void {
const data = input[0..len];
const hash = Keccak256.hash(data);
@memcpy(output[0..32], &hash);
}
export fn alloc(len: usize) ?[*]u8 {
const slice = std.heap.wasm_allocator.alloc(u8, len) catch return null;
return slice.ptr;
}
export fn free(ptr: [*]u8, len: usize) void {
std.heap.wasm_allocator.free(ptr[0..len]);
}
Zig-to-Rust
Rust is used for complex elliptic curve operations via arkworks.Cargo Configuration
Copy
Ask AI
# Cargo.toml
[lib]
crate-type = ["staticlib"]
name = "crypto_wrappers"
[dependencies]
ark-bn254 = "0.4"
ark-bls12-381 = "0.4"
ark-ec = "0.4"
ark-ff = "0.4"
[features]
default = ["asm"]
asm = ["keccak-asm"]
portable = ["tiny-keccak"] # For WASM
Rust FFI Functions
Copy
Ask AI
// src/rust/bn254.rs
use ark_bn254::{Fr, G1Affine, G1Projective};
use ark_ec::CurveGroup;
use ark_ff::PrimeField;
#[no_mangle]
pub extern "C" fn bn254_g1_add(
ax: *const u8,
ay: *const u8,
bx: *const u8,
by: *const u8,
out_x: *mut u8,
out_y: *mut u8,
) -> i32 {
// Safety: caller ensures valid pointers
unsafe {
let a = match read_g1_point(ax, ay) {
Some(p) => p,
None => return -1,
};
let b = match read_g1_point(bx, by) {
Some(p) => p,
None => return -1,
};
let result = (a + b).into_affine();
write_g1_point(&result, out_x, out_y);
0
}
}
unsafe fn read_g1_point(x: *const u8, y: *const u8) -> Option<G1Projective> {
let x_bytes = std::slice::from_raw_parts(x, 32);
let y_bytes = std::slice::from_raw_parts(y, 32);
// ... parse and validate point
Some(G1Affine::new(x_field, y_field).into())
}
Zig Bindings
Copy
Ask AI
// bn254_ffi.zig
const c = @cImport({
@cInclude("crypto_wrappers.h");
});
pub const G1Point = struct {
x: [32]u8,
y: [32]u8,
};
pub fn g1Add(a: G1Point, b: G1Point) !G1Point {
var result: G1Point = undefined;
const status = c.bn254_g1_add(
&a.x, &a.y,
&b.x, &b.y,
&result.x, &result.y,
);
if (status != 0) return error.InvalidPoint;
return result;
}
test "G1 point addition" {
const generator = G1Point{
.x = [_]u8{1} ++ [_]u8{0} ** 31,
.y = [_]u8{2} ++ [_]u8{0} ** 31,
};
const result = try g1Add(generator, generator);
// Verify 2G
try std.testing.expect(result.x[0] != 0);
}
Zig-to-C
C libraries are used for mature, audited implementations.Vendored Libraries
Copy
Ask AI
lib/
├── blst/ # BLS12-381 signatures
│ ├── src/
│ └── bindings/
├── c-kzg-4844/ # KZG commitments
│ ├── src/
│ └── bindings/
└── libwally-core/ # Wallet utilities (git submodule)
└── src/
Build Integration
Copy
Ask AI
// build.zig
const blst = b.addStaticLibrary(.{
.name = "blst",
.target = target,
.optimize = optimize,
});
blst.addCSourceFiles(.{
.files = &.{
"lib/blst/src/server.c",
// ... other files
},
.flags = &.{"-O3", "-fno-builtin"},
});
blst.linkLibC();
// Link to main library
lib.linkLibrary(blst);
Zig C Imports
Copy
Ask AI
// bls12381_ffi.zig
const c = @cImport({
@cInclude("blst.h");
});
pub fn sign(secret_key: [32]u8, message: []const u8) [96]u8 {
var sig: c.blst_p2_affine = undefined;
var sk: c.blst_scalar = undefined;
// Import secret key
c.blst_scalar_from_bendian(&sk, &secret_key);
// Sign
var hash: c.blst_p2 = undefined;
c.blst_hash_to_g2(
&hash,
message.ptr, message.len,
"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_",
43,
null, 0,
);
var sig_point: c.blst_p2 = undefined;
c.blst_sign_pk_in_g1(&sig_point, &hash, &sk);
c.blst_p2_to_affine(&sig, &sig_point);
// Serialize
var output: [96]u8 = undefined;
c.blst_p2_affine_compress(&output, &sig);
return output;
}
C Header Generation
Auto-generate C headers from Zig:Copy
Ask AI
zig build generate-header
src/primitives.h
Copy
Ask AI
// primitives.h (auto-generated)
#ifndef PRIMITIVES_H
#define PRIMITIVES_H
#include <stdint.h>
#include <stddef.h>
void keccak256(const uint8_t* input, size_t len, uint8_t* output);
int secp256k1_sign(
const uint8_t* private_key,
const uint8_t* message_hash,
uint8_t* signature,
uint8_t* recovery_id
);
// ... more exports
#endif
Cross-Language Testing
Zig Tests with C
Copy
Ask AI
test "blst signature verification" {
const secret_key = [_]u8{1} ** 32;
const message = "test message";
const signature = sign(secret_key, message);
const public_key = derivePublicKey(secret_key);
try std.testing.expect(verify(public_key, message, signature));
}
TypeScript Tests with Native
Copy
Ask AI
// Cross-validate native vs WASM
describe("native/wasm parity", () => {
it("produces identical keccak256 hashes", () => {
const data = new TextEncoder().encode("test");
const native = nativeKeccak256(data);
const wasm = wasmKeccak256(data);
expect(native).toEqual(wasm);
});
});
Fuzzing Across Languages
Copy
Ask AI
// Fuzz test cross-language consistency
describe("fuzz", () => {
it("native matches wasm for random inputs", () => {
for (let i = 0; i < 10000; i++) {
const data = crypto.getRandomValues(
new Uint8Array(Math.floor(Math.random() * 1000))
);
const native = nativeKeccak256(data);
const wasm = wasmKeccak256(data);
expect(native).toEqual(wasm);
}
});
});
Error Handling Across Languages
Zig Errors
Copy
Ask AI
pub const CryptoError = error{
InvalidPoint,
InvalidScalar,
SignatureFailed,
VerificationFailed,
};
pub fn verify(sig: Signature) CryptoError!bool {
if (!isValidSignature(sig)) return error.InvalidSignature;
// ...
}
C Return Codes
Copy
Ask AI
// Convention: 0 = success, negative = error
#define CRYPTO_SUCCESS 0
#define CRYPTO_ERROR_INVALID_INPUT -1
#define CRYPTO_ERROR_VERIFICATION_FAILED -2
Rust Result Types
Copy
Ask AI
#[repr(C)]
pub struct CryptoResult {
success: bool,
error_code: i32,
}
#[no_mangle]
pub extern "C" fn verify_signature(...) -> CryptoResult {
match internal_verify(...) {
Ok(valid) => CryptoResult { success: valid, error_code: 0 },
Err(e) => CryptoResult { success: false, error_code: e.code() },
}
}
TypeScript Error Translation
Copy
Ask AI
// errors.ts
export function translateError(code: number): Error {
switch (code) {
case -1:
return new InvalidInputError();
case -2:
return new VerificationFailedError();
default:
return new CryptoError(`Unknown error: ${code}`);
}
}
// Usage
const result = lib.symbols.verify_signature(...);
if (result.error_code !== 0) {
throw translateError(result.error_code);
}
Build Dependencies
Full Build Chain
Copy
Ask AI
# 1. Rust builds first (static library)
cargo build --release
# Output: target/release/libcrypto_wrappers.a
# 2. C libraries built by Zig
zig build deps
# Output: zig-out/lib/libblst.a, libc_kzg.a
# 3. Main Zig build links everything
zig build
# Output: native libs, WASM
# 4. TypeScript bundles
bun run build:dist
# Output: dist/
Dependency Graph
Copy
Ask AI
Cargo.toml ──► libcrypto_wrappers.a ──┐
│
lib/blst ──────► libblst.a ───────────┼──► Zig Build ──► WASM/Native
│
lib/c-kzg ─────► libc_kzg.a ──────────┘
│
▼
TypeScript Bundle

