Stoffel
July 3, 2026 · View on GitHub
Stoffel is a toolchain for writing, compiling, and running programs that compute over private data using secure Multi-Party Computation (MPC). You write ordinary looking code, mark the values that must stay secret, and Stoffel compiles and executes it so that no single party ever sees the secret inputs in the clear.
This repository is the Stoffel monorepo. It contains everything from the language and compiler down to the runtime and the networking/MPC layer:
| Product | Crate | What it is |
|---|---|---|
| Stoffel CLI | crates/stoffel-cli | The Cargo-like stoffel command for creating, building, and running MPC projects |
| StoffelLang | crates/stoffel-lang | The stoffel language compiler (.stfl → .stflb bytecode) |
| Stoffel SDK | crates/stoffel-rust-sdk | The Rust SDK (stoffel crate) for embedding compilation, execution, and MPC config in apps |
| Stoffel VM | crates/stoffel-vm | The register-based VM runtime, networking, and MPC backends (HoneyBadger, AVSS), plus the C FFI |
Supporting crates:
crates/stoffel-vm-runner: thestoffel-runbinary — local runner and distributed MPC party/client nodecrates/stoffel-vm-types: shared VM types, the instruction set, runtimeValues, and the compiled bytecode formatcrates/stoffel-bindgen: build-time generation of typed Rust bindings for Stoffel programsinclude/: the public C header and FFI notes for embedding the VM from C-compatible environments
StoffelLang source (.stfl)
│ stoffel build / stoffel-lang
▼
Compiled bytecode (.stflb) ← stoffel-vm-types::compiled_binary
│ stoffel run / stoffel-run / Stoffel SDK
▼
Stoffel VM ── local execution (clear or simulated MPC)
└─ distributed MPC (HoneyBadger / AVSS over QUIC)
Installation
Install the released Stoffel CLI with the installer:
curl -fsSL https://get.stoffelmpc.com | sh
The installer places stoffel in ~/.local/bin by default. Add it to your shell path if needed:
export PATH="$HOME/.local/bin:$PATH"
stoffel --help
Create and run a project:
stoffel init hello-mpc
cd hello-mpc
stoffel run --input a=40 --input b=2
Runner caveat: local runs need the
stoffel-runMPC runner. The installer drops it next tostoffel, and the CLI discovers it automatically. If you installedstoffelanother way, Stoffel also looks forstoffel-runon yourPATH(e.g. aftercargo install stoffel-vm-runner), or you can point at a specific binary with--runner <path>or theSTOFFEL_RUN_BINenvironment variable.
To build from source instead, see Build and Test.
Stoffel CLI
The stoffel binary is a Cargo-like project CLI built on top of crates/stoffel-rust-sdk.
It reads Stoffel.toml, defaults to src/main.stfl, and writes bytecode to
target/debug/<package>.stflb or target/release/<package>.stflb.
Local runner: local execution (
stoffel runwithout--network, andstoffel dev) drives thestoffel-runMPC runner. Stoffel resolves it in order: an explicit--runner <path>, theSTOFFEL_RUN_BINenvironment variable, astoffel-runsitting next to thestoffelbinary (where the installer puts it), astoffel-runon yourPATH(e.g. viacargo install stoffel-vm-runner), then astoffel-runbuilt in the current Cargo workspace. See Build and Test to build one from source.
Create a project
stoffel init my-lib --lib
stoffel init rust-app --template rust
stoffel init py-app --template python
Solidity/EVM project templates (solidity-foundry, solidity-hardhat) are
planned but not yet available — see Roadmap.
Build, check, and inspect
stoffel build
stoffel check
stoffel compile src/main.stfl -O2 --output target/debug/hello-mpc.stflb
stoffel compile --disassemble target/debug/hello-mpc.stflb
build and compile default to all src/**/*.stfl files when no source path is
provided. Use --output when compiling a single file.
Run
stoffel run target/debug/hello-mpc.stflb --entry main --input a=40 --input b=2
stoffel run --input a=40 --input b=2
stoffel run program.stfl --local --client-input 0=42 --parties 5 --threshold 1
stoffel run program.stfl --local --expected-output-clients 2
stoffel run target/debug/program.stflb --network --config offchain-client.toml --input x=42
stoffel run target/debug/program.stflb --network --config party-network.toml --connect-timeout-ms 1000
run accepts .stfl source or .stflb bytecode. By default it runs through the
local MPC coordinator; --local is accepted as an explicit local mode selector.
Use --client-input SLOT=VALUE for ClientStore input providers, and
--expected-output-clients N to declare output-capable local client slots
0..N-1 for dynamic output loops or output-only runs (this does not synthesize
client inputs). --network --config uses SDK network configuration: an off-chain
client config executes through the coordinator/node RPC path, while a network
config validates and connects to real node addresses.
Develop with live reload
stoffel dev --parties 5 --threshold 1 --input a=40 --input b=2
stoffel dev runs once, watches Stoffel.toml and the configured source tree,
then rebuilds and reruns whenever a .stfl file or project config changes. Use
stoffel dev --once for one-shot behavior, or --poll-ms <N> to tune reload latency.
Test and manage projects
stoffel test
stoffel test --test selected --verbose
stoffel status --verbose
stoffel clean
stoffel clean --all
stoffel update --check
stoffel update
status validates project config, checks detected dependency managers, compiles
configured sources, and reports local MPC network configuration. clean removes
the project target/ directory and Stoffel build cache; --all also removes
known ecosystem caches such as node_modules, Foundry cache/output, and Python
test caches. update checks for CLI/project dependency updates and runs detected
project dependency update commands; use --check to inspect without changing files.
StoffelLang
StoffelLang (.stfl) is the source language compiled by crates/stoffel-lang
into Stoffel VM bytecode. It is a Python-flavored, statically typed language: it
uses indentation and def, but values carry concrete types and the secret
qualifier marks data that must remain private under MPC.
def main(a: secret int64, b: secret int64) -> secret int64:
return a + b
Arithmetic, comparisons, and control flow work transparently on both clear and
secret values; the compiler and VM insert the MPC operations needed for secret
operands. Programs interact with the runtime through module-style builtins such
as Share.*, Field.*, and Mpc.* (see Builtins).
Many worked programs live in crates/stoffel-lang/examples/, including local
collections/control-flow demos, AES-128 (CTR/CBC/circuit) under MPC, and
threshold ECDSA / certificate-signing flows. See
crates/stoffel-lang/examples/README.md for an index.
The compiler can be driven directly (stoffel compile) or through the SDK; bump
the binary format version when serialization changes (see
crates/stoffel-vm-types/src/compiled_binary/).
Stoffel SDK (Rust)
crates/stoffel-rust-sdk publishes the stoffel crate: the library entry point
for embedding Stoffel-Lang compilation, bytecode loading, VM execution, and MPC
participant configuration in Rust applications. The CLI itself is built on it.
# Cargo.toml
stoffel-rust-sdk = "0.1.0"
use stoffel::prelude::*;
let result = Stoffel::compile(
"def main(a: int64, b: int64) -> int64:\n return a + b",
)?
.with_inputs(&[("a", 42_i64), ("b", 58_i64)])
.execute_clear()?;
assert_eq!(result[0].as_i64(), Some(100));
# Ok::<(), stoffel::Error>(())
For local MPC smoke runs, use the same builder and call execute_local().await.
This starts real localhost VM parties through stoffel-vm's local coordinator
runner when a built stoffel-run binary is available:
use stoffel::prelude::*;
# async fn example() -> stoffel::Result<()> {
let result = Stoffel::compile(
"def main(a: secret int64, b: secret int64) -> secret int64:\n return a + b",
)?
.parties(5)
.threshold(1)
.execute_local()
.await?;
# Ok(())
# }
crates/stoffel-bindgen complements the SDK by generating typed Rust bindings
for a Stoffel program at build time, so host code can call into compiled
programs with a checked interface.
Stoffel VM
stoffel-vm is a register machine optimized for MPC. The register design keeps
execution predictable and maps cleanly onto optimized runtimes and physical MPC
backends. It supports basic values (integers, booleans, strings, floats) and
complex runtime types (objects, arrays, closures, foreign objects, and secret
shares), and has a closure system with true lexical scoping where functions
capture upvalues from their surrounding environment.
Stoffel supports Rust ⇆ Stoffel FFI out of the box, so you can extend the runtime with native Rust functions and objects while keeping the execution model intact. A configurable hook system can intercept instruction execution, register access, stack events, object/array access, closure creation, and more for debugging or instrumentation.
HoneyBadger and AVSS MPC backends are built by default. Distributed party runs
select the backend from the compiled .stflb program manifest.
Embedding the VM directly
The most direct low-level use of the runtime is to embed it in a Rust program
and register VMFunction values. VirtualMachine::new() automatically registers
the standard library and MPC builtins. (Most applications should prefer the SDK
above; this is the raw API.)
use std::collections::HashMap;
use stoffel_vm::core_types::Value;
use stoffel_vm::core_vm::VirtualMachine;
use stoffel_vm::functions::VMFunction;
use stoffel_vm::instructions::Instruction;
fn main() -> Result<(), String> {
let mut vm = VirtualMachine::new();
let hello_world = VMFunction::new(
"hello_world".to_string(),
vec![],
vec![],
None,
2,
vec![
Instruction::LDI(0, Value::String("Hello, World!".to_string())),
Instruction::PUSHARG(0),
Instruction::CALL("print".to_string()),
Instruction::LDI(1, Value::Unit),
Instruction::RET(1),
],
HashMap::new(),
);
vm.try_register_function(hello_world)?;
let result = vm.execute("hello_world")?;
println!("Program returned: {:?}", result);
Ok(())
}
Good places to explore next:
crates/stoffel-vm-types/examples/generate_client_mul_program.rs— a bytecode-generation examplecrates/stoffel-vm/src/tests/vm_mpc_integration.rs— VM + MPC execution flowstests/p2p_integration.rs— QUIC networking coverage
Instruction Set
Memory Operations
LD(dest_reg, stack_offset): Load a value from the current activation record into a registerLDI(dest_reg, value): Load an immediate value into a registerMOV(dest_reg, src_reg): Move a value from one register to anotherPUSHARG(reg): Push a register value as a function argument
Arithmetic Operations
ADD,SUB,MUL,DIV,MOD(dest_reg, src1_reg, src2_reg)
Bitwise Operations
AND,OR,XOR(dest_reg, src1_reg, src2_reg)NOT(dest_reg, src_reg)SHL,SHR(dest_reg, src_reg, amount_reg)
Control Flow
JMP(label): Unconditional jumpJMPEQ,JMPNEQ,JMPLT,JMPGT(label): Conditional jumpsCMP(reg1, reg2): Compare two registersCALL(function_name): Call a functionRET(reg): Return from the current function with the value in a register
Values
Value::I64/I32/I16/I8 — signed integers
Value::U64/U32/U16/U8 — unsigned integers
Value::Float(F64) — 64-bit floating point
Value::Bool(bool) — boolean
Value::String(String) — string
Value::Object(ObjectRef) — object table reference
Value::Array(ArrayRef) — array table reference
Value::Foreign(ForeignObjectRef) — foreign object reference
Value::Closure(Arc<Closure>) — closure with captured environment
Value::Unit — unit/void/nil
Value::Share(ShareType, ShareData) — secret-shared value for MPC
Standard Library Builtins
General runtime builtins registered by default:
print/type: print values; get a value's type as a stringcreate_object/create_array: create an object or arrayget_field/set_field: get/set a field on an object or arrayarray_length/array_push: length of an array; append valuescreate_closure/call_closure: create and invoke closuresget_upvalue/set_upvalue: read/update captured upvaluesClientStore.*: client slot counts andtake_share/take_share_fixedMpcOutput.send_to_client: send a share result to a client
MPC-focused, module-style builtins:
Share.*: clear-to-share conversion, arithmetic on shares, opening, random share generation, client output, local interpolation, and commitment inspectionMpc.*: runtime MPC metadata such as party id, threshold, instance id, readiness, and randomness helpersRbc.*: reliable broadcast helpersCrypto.*: hashing and curve/field conversion helpersBytes.*: byte-array helpersAvss.*: AVSS-specific helper functions
Compiled Bytecode
Stoffel ships a portable compiled binary format through
stoffel-vm-types::compiled_binary::CompiledBinary. The format uses the magic
bytes STFL and round-trips between VMFunction definitions and serialized binaries.
use stoffel_vm_types::compiled_binary::{utils::save_to_file, CompiledBinary};
// Assume `functions: Vec<VMFunction>` already exists.
let binary = CompiledBinary::from_vm_functions(&functions);
save_to_file(&binary, "program.stflb").unwrap();
VM Runner CLI (stoffel-run)
stoffel-vm-runner provides stoffel-run, which executes a compiled Stoffel
bytecode file locally or as part of a distributed MPC session.
cargo build --release -p stoffel-vm-runner
cargo run -p stoffel-vm-runner --bin stoffel-run -- --help
Run a compiled program locally (default entry function is main):
./target/release/stoffel-run path/to/program.stflb
./target/release/stoffel-run path/to/program.stflb main --trace-instr
Run a leader node for a 5-party MPC session:
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
./target/release/stoffel-run path/to/program.stflb main \
--leader \
--bind 127.0.0.1:9000 \
--n-parties 5 \
--threshold 1
Join as another party:
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
./target/release/stoffel-run path/to/program.stflb main \
--party-id 1 \
--bootstrap 127.0.0.1:9000 \
--bind 127.0.0.1:9002 \
--n-parties 5 \
--threshold 1
Run in client mode to submit inputs to the party servers:
./target/release/stoffel-run --client \
--inputs 10,20 \
--servers 127.0.0.1:10000,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004,127.0.0.1:9005 \
--n-parties 5
AVSS output-client mode can reconstruct private field outputs:
./target/release/stoffel-run --client \
--mpc-backend avss \
--mpc-curve secp256k1 \
--inputs 0x<sha256-tbs-digest-hex> \
--outputs 2 \
--servers 127.0.0.1:9000,127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004 \
--n-parties 5
Notes:
STOFFEL_AUTH_TOKENis required for authenticated discovery in bootnode, leader, and party flows- The CLI accepts any file path; this repository conventionally stores compiled fixtures as
.stflb --mpc-backendsupportshoneybadgerandavssfor client mode;.stflbparty runs use the backend recorded in the program manifest and reject conflicting CLI overrides--mpc-curvesupportsbls12-381,bn254,curve25519,ed25519,secp256k1, andp-256(secp256r1) for AVSS
Docker Flows
The API/coordinator topology is runnable with the reserve-index compose stack:
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
docker compose -f docker-compose.coordinator.reserve-index.yml up --build
That coordinator path runs through the HoneyBadger/BLS12-381 VM path. The AVSS compose stack is separate and covers AVSS curves and local share storage:
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
docker compose -f docker-compose.avss.yml up --build
docker-compose.avss.yml mounts a per-party local data volume and forwards STOFFEL_LOCAL_STORE to stoffel-run.
The AVSS threshold ECDSA examples mirror the threshold signature fixtures:
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
STOFFEL_PROGRAM=/app/programs/threshold_ecdsa_secp256k1.stflb \
STOFFEL_MPC_CURVE=secp256k1 \
docker compose -f docker-compose.avss.yml up --build
STOFFEL_AUTH_TOKEN=replace-with-random-secret \
STOFFEL_PROGRAM=/app/programs/threshold_ecdsa_p256.stflb \
STOFFEL_MPC_CURVE=p-256 \
docker compose -f docker-compose.avss.yml up --build
The Stoffel source for these programs lives in crates/stoffel-lang/examples/threshold_signatures/threshold_ecdsa_secp256k1/main.stfl and crates/stoffel-lang/examples/threshold_signatures/threshold_ecdsa_p256/main.stfl. The VM only provides primitive helpers for field inversion, converting an opened curve point to x mod q, and formatting the final ECDSA output. The threshold ECDSA protocol itself is expressed in the Stoffel program. The returned layout is fixed-width big-endian r(32) || s(32) || sec1_compressed_pk(33), so callers can DER-encode (r, s) directly.
For the AVSS certificate-signing path, run /app/programs/avss_certificate_keygen.stflb with STOFFEL_MPC_CURVE=secp256k1 or STOFFEL_MPC_CURVE=p-256 to persist each party's CA signing share. Keygen is idempotent: it loads the existing share if the storage key already exists and only generates on first use. Then run /app/programs/avss_certificate_sign.stflb with STOFFEL_WAIT_FOR_CLIENTS=1; the client submits the real SHA-256 TBS digest and reconstructs fixed-width threshold ECDSA r || s material with --outputs 2. The corresponding Stoffel source lives in crates/stoffel-lang/examples/avss_certificate/keygen/main.stfl and crates/stoffel-lang/examples/avss_certificate/sign/main.stfl.
C Foreign Function Interface
stoffel-vm builds as both an rlib and a cdylib, so the runtime can also be embedded from C-compatible environments.
Relevant files:
include/stoffel_vm.hinclude/README.md
Platform-specific library names:
- Linux:
libstoffel_vm.so - macOS:
libstoffel_vm.dylib - Windows:
stoffel_vm.dll
Build and Test
Build everything:
cargo build
Run the test suite:
cargo test
cargo test -- --ignored
Build the runtime and CLI in release mode:
cargo build --release -p stoffel-vm -p stoffel-vm-runner
HoneyBadger and AVSS backend code is built by default. Distributed party runs
select the backend from the compiled .stflb program manifest.
Roadmap
Stoffel is under active development. The following capabilities are planned or in progress and are not yet supported:
- Solidity / EVM integration —
solidity-foundryandsolidity-hardhatproject templates, and tooling for using Stoffel MPC outputs from on-chain smart contracts. Referenced in some docs, but not yet shipped. - Additional SDKs — Python and TypeScript/WASM SDKs to complement the Rust SDK. The C FFI surface exists today; higher-level language bindings are still in progress.
- Persistent / long-running MPC networks — keeping nodes and the coordinator warm across runs with ahead-of-time preprocessing, so repeated runs pay only the online cost.
- Hosted / managed deployment — turnkey deployment of MPC party networks beyond the local runner and Docker Compose stacks.
Have a use case you don't see here? Open an issue or reach out — priorities are shaped by what people are building.
Learn More
To learn more about what you can build with Stoffel, visit stoffelmpc.com.