# Architecture Source: https://docs.stoffelmpc.com/architecture/architecture Technical overview of Stoffel VM's register-based architecture with separate clear and secret value handling. Stoffel VM is a register based virtual machine with two sets of registers. Clear registers for manipulating non-secret values. Secret registers are used for manipulating secret values. Stoffel VM execution model showing bytecode, the instruction dispatcher, clear and secret registers, runtime stores, and MPC protocol hooks. Campaign-style Stoffel stack diagram showing how applications, SDKs, Stoffel language and CLI, VM bytecode, and the privacy backend layer together. ## Technical Overview ### [Virtual Machine Architecture](./subsections/virtualmachine) * What type of VM is stoffel * Clear vs Secret values ### [Instruction Set](./subsections/instructionset) * Complete reference of supported VM instructions * Opcode specifications and behavior * Optimization opportunities ### [Builtin Types](./subsections/builtintypes) * Overview of core data types (numbers, strings, arrays, etc.) * Type conversion and manipulation * Memory representation and optimization ### [Activation Records](./subsections/activationrecords) * Call stack management and function invocation * Local variable scoping and lifetime * Optimizing stack frame allocation ### [VM Functions](./subsections/vmfunctions) * Virtual machine architecture overview * Execution model and stack management * Error handling ### [Closures Overview](./subsections/closures) * Lexical scoping and variable capture * Implementation details and memory management ### [Foreign Function Interface](./subsections/foreignfunctioninterface) * Integrating with external libraries and systems * Data marshalling and type conversion * Performance considerations for FFI calls ### [Builtin Methods](./subsections/builtinmethods) * Standard library functions and utilities * Common operations for each data type ### [Runtime Hooks](./subsections/runtimehooks) * Extension points for monitoring and customization * Performance profiling and instrumentation * Debugging facilities ## Why a Register Machine? The choice of a register-based architecture over a stack-based design was driven by several key factors: 1. **Parallelization Opportunities** * Register machines allow for easier identification of independent instructions * Multiple instructions can be executed in parallel, reducing overall execution time * Better suited for modern hardware architectures 2. **Communication Efficiency** * Reduced number of memory access operations * Fewer rounds of communication in Multi-Party Computation (MPC) contexts * More efficient instruction encoding 3. **Optimization Potential** * Direct access to operands enables better optimization strategies * Easier to implement specialized instructions * More straightforward analysis of data flow ## Why dedicated clear and secret registers 1. **Implicit reveal and hide** * Having dedicated registers for secret and clear values allows us to implicitly reveal and hide values as they're moved between registers. * Separation of registers allows for optimizations to be applied specifically to clear or secret operations. * Avoids having to track the type of the virtual register during runtime as values may become secret shared or reveal through the course of execution. # MPC Integration Source: https://docs.stoffelmpc.com/architecture/mpc How Stoffel integrates with MPC backends, secret sharing, client inputs, and backend-specific security models. Use this page to understand how private client inputs move through Stoffel: input collection, secret sharing, VM execution, openings, and client-output delivery. If you are deciding which backend to use, start with [MPC Backends](../mpc-protocols/overview). If you already chose a backend and need config or runtime details, use [How Backend Selection Works](../mpc-protocols/implementation). HoneyBadger MPC runtime diagram showing client input/output paths, coordinator-managed sessions, preprocessing, and protocol rounds among parties. Campaign-style networked privacy backend diagram showing application clients, coordinator-managed sessions, preprocessing, party mesh communication, and authorized output delivery. ## Protocol Architecture ## Protocol architecture ```text theme={null} StoffelLang program │ ▼ Compiled bytecode manifest │ ├── backend: HoneyBadgerMPC | AVSS ├── curve/field metadata ├── client input/output schema └── preprocessing demand │ ▼ VM + selected MPC backend ``` ## Current backend support | Backend | Purpose | Developer selector | | -------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | HoneyBadgerMPC | Default asynchronous/robust backend for field-compatible secret application values. | `honeybadger` / `MpcBackend::HoneyBadger` | | AVSS | AVSS backend for verifiable scalar shares, public commitments, and curve-compatible outputs. | `avss`, `avss:` / `MpcBackend::Avss { curve }` | Both backends use the same high-level Stoffel application model, but they are not interchangeable at the cryptographic layer. HoneyBadgerMPC is field-arithmetic oriented. AVSS is group/scalar oriented and is the backend to use when the output boundary needs public commitments, curve-encoded values, or scalar responses that match an external verifier. ## Integration points ### Stoffel VM * Secret register operations map to backend share operations. * Clear-to-secret transitions create backend share data. * Reveal/open operations reconstruct values through the selected backend. * `Share.random`, `Share.open`, `Share.get_commitment`, and `Mpc.*` builtins route through backend capabilities. ### CLI and bytecode * `Stoffel.toml` stores `[mpc] backend`, optional `curve`, `parties`, and `threshold`. * CLI flags such as `--backend`, `--field`, `--parties`, and `--threshold` override project settings. * `.stflb` bytecode records backend and curve/field metadata so execution can validate the runtime configuration. ### Rust SDK * `MpcConfig::builder()` configures parties, threshold, instance ID, and backend. * Program builders accept `.backend(...)` and `.curve(...)`. * `NetworkDeployment` and off-chain client configs carry backend selection into generated network/client TOML. ## Security model Each backend has its own threat model and assumptions. Do not treat “MPC backend” as a single security claim. | Backend | Security posture | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | HoneyBadgerMPC | Asynchronous, Byzantine-robust, field-based MPC for private computation over application values. Use it when the output boundary is an opened result or client-output shares. | | AVSS | Asynchronous verifiable secret sharing with Feldman-style commitments. Use it when public commitments, curve-encoded values, or scalar responses are part of the verifier-facing protocol boundary. | Current Stoffel local/network config validates: ```text theme={null} parties >= 4 * threshold + 1 ``` Use five parties and threshold one for local development unless you are intentionally testing a larger topology. ## Data protection flow * Inputs become secret shares through ClientStore or direct secret-value paths. * Computation proceeds over backend share data. * Intermediate secret values remain secret unless the program opens/reveals them. * Outputs are returned, opened, or sent to client slots only where the program explicitly does so. ## Performance considerations | Operation shape | Notes | | ------------------------------- | ------------------------------------------------------------------------------------------------------------- | | Local share arithmetic | Addition, subtraction, and scalar operations are usually cheap. | | Secret multiplication | Requires backend protocol work and preprocessing. | | Opening/reveal | Requires reconstruction from enough parties. | | Comparisons and bit-heavy logic | More expensive than field arithmetic because they lower into additional share operations. | | Curve/commitment workflows | Use AVSS when the output boundary needs public commitments, curve-encoded values, or opened scalar responses. | ## See also * [MPC Protocols](../mpc-protocols/overview) * [How Backend Selection Works](../mpc-protocols/implementation) * [HoneyBadgerMPC](../mpc-protocols/honeybadger-mpc) * [AVSS](../mpc-protocols/avss) * [Stoffel VM Overview](../stoffel-vm/overview) * [Rust SDK API](../rust-sdk/api) # Design Rationale Source: https://docs.stoffelmpc.com/architecture/rationale Design decisions behind Stoffel's protocol-agnostic architecture, extensibility, and future-proofing strategies. ## Protocol Agnostic Design The virtual machine is designed to be protocol-agnostic for several reasons: 1. **Flexibility** * Support for different MPC protocols without architectural changes * Easy integration of new protocols as they are developed * Ability to switch protocols based on specific requirements 2. **Future-Proofing** * Not tied to limitations of specific protocols * Can adapt to advances in MPC research * Supports hybrid protocol approaches ## Extensibility The architecture emphasizes extensibility through: 1. **Modular Design** * Clear separation of concerns * Plugin system for new instructions * Customizable optimization passes 2. **Abstract Interfaces** * Protocol-independent instruction definitions * Flexible memory model * Extensible register system # Sources Source: https://docs.stoffelmpc.com/architecture/sources Comprehensive list of resources and references used when designing and building the Stoffel framework. This is a comprehensive list of resources or referenced when designing and making Stoffel. This work was created through a mix of independent research and development plus outside sources listed below, though as Newton once said, 'If I have seen further, it is by standing on the shoulders of giants.' Any similarities to existing works not listed are purely coincidental and a testament to the universal nature of good ideas. **Incomplete List Disclaimer** This list of sources attempts to be as complete as possible. Some sources may have been forgotten or haven't found their way into the list yet! * [LUA 5.4](https://www.lua.org/source/5.4/) # System Architecture Source: https://docs.stoffelmpc.com/architecture/system Complete Stoffel system architecture showing how CLI, compiler, VM, SDKs, and MPC protocols interact. This section provides an overview of the complete Stoffel system architecture and how the various components interact. ## Overall System Design Campaign-style Stoffel architecture diagram showing developer application code, build artifacts, coordinator-managed execution, VM parties, and authorized outputs. The same `.stflb` artifact connects the application-facing build path to the runtime party mesh. App code prepares protected inputs, the coordinator manages lifecycle and IO routing, and VM parties compute over shares. ## Component Interactions ### Development Workflow 1. **Project Creation**: Stoffel CLI creates project structure with templates 2. **Code Writing**: Developers write StoffelLang programs with MPC primitives 3. **Compilation**: StoffelLang compiler generates optimized VM bytecode 4. **Testing**: Local VM execution for development and testing 5. **Deployment**: MPC network deployment with protocol integration ### Runtime Execution 1. **Program Loading**: Stoffel VM loads compiled bytecode 2. **Secret Sharing**: Input data is secret-shared across MPC nodes 3. **Secure Computation**: VM executes with MPC protocol integration 4. **Result Reconstruction**: Output is reconstructed from secret shares ## Data Flow Campaign-style networked privacy backend diagram showing clients, coordinator, preprocessing, party mesh, and encrypted or authorized outputs. ### Clear Data Path * Public inputs and configuration data * Direct VM register operations * No cryptographic overhead * Immediate availability across all nodes ### Secret Data Path * Private inputs requiring protection * Automatic secret sharing on input * MPC protocol operations during computation * Selective reveal for output reconstruction ## Security Architecture ### Isolation Boundaries * **Process Isolation**: Each MPC node runs in isolated environment * **Memory Protection**: Clear and secret data separation * **Network Security**: Encrypted communication between nodes * **Access Control**: Role-based access to computation resources ### Trust Model * **Honest Majority**: Assumes majority of nodes are honest * **Semi-Honest Adversary**: Protects against passive attacks * **Input Privacy**: Individual inputs remain private * **Computation Privacy**: Intermediate values are protected ## Scalability Design ### Horizontal Scaling * **Node Addition**: Dynamic addition of MPC nodes * **Load Distribution**: Computation workload balancing * **Geographic Distribution**: Global node deployment support ### Vertical Scaling * **Resource Optimization**: Efficient CPU and memory usage * **Parallel Execution**: Multi-threaded computation where possible * **Caching Strategies**: Optimized data and computation caching ## Integration Points ### External Systems * **Database Integration**: Secure querying of external databases * **API Integration**: RESTful APIs for system interaction * **Blockchain Integration**: Smart contract integration for verification ### Development Tools * **IDE Support**: Language server protocol integration * **Debugging Tools**: Comprehensive debugging and profiling * **Testing Frameworks**: Specialized testing for MPC applications This architecture enables secure, scalable, and developer-friendly multi-party computation while maintaining strong security guarantees. # VM Architecture Details Source: https://docs.stoffelmpc.com/architecture/vm Detailed Stoffel VM architecture including dual register spaces for clear and secret values in MPC operations. This section covers the detailed architectural design of Stoffel VM. For implementation details and current status, see [Stoffel VM Implementation Details](../stoffel-vm/implementation). Stoffel VM execution model showing bytecode, the instruction dispatcher, clear registers, secret registers, runtime stores, and MPC protocol hooks. ## Register Architecture Stoffel VM uses a register-based architecture with two distinct register spaces: ### Clear Registers * Store public/non-secret values * Direct CPU register mapping for performance * Standard arithmetic and logical operations * No cryptographic overhead ### Secret Registers * Store secret-shared values for MPC * Protocol-agnostic secret handling * Automatic secret sharing and reconstruction * MPC-optimized operations ## Memory Model ### Object Store * Dynamic object allocation with reference counting * Key-value mappings for flexible data structures * Garbage collection integration points ### Array Store * Contiguous memory layout for arrays * Dynamic resizing capabilities * Index bounds checking ### Stack Management * Function call activation records * Parameter passing via argument stack * Local variable storage ## Instruction Pipeline ### Fetch-Decode-Execute Cycle 1. **Instruction Fetch**: Retrieve next instruction from program counter 2. **Decode**: Parse instruction opcode and operands 3. **Execute**: Perform operation with register/memory access 4. **Writeback**: Store result in destination register ### Hook Integration Points * Pre-instruction hooks for debugging * Post-instruction hooks for monitoring * Register access hooks for MPC protocol integration * Memory operation hooks for garbage collection ## Type System Integration ### Value Types The VM supports a rich type system with runtime type information: * Primitive types (integers, floats, booleans, strings) * Complex types (objects, arrays, closures) * Foreign objects for host language integration ### Type Safety * Runtime type checking for operations * Type coercion rules for mixed operations * Error handling for type mismatches ## Closure System ### Lexical Scoping * True lexical scoping with upvalue capture * Closure creation with environment capture * Upvalue sharing between closures ### Function Calls * Dynamic function dispatch * Parameter binding and local variable allocation * Return value handling ## Protocol Integration ### MPC Protocol Interface * Abstract protocol operations for secret sharing * Reveal operations for secret-to-clear transitions * Communication round optimization ### Clear/Secret Transitions * Automatic hiding (clear → secret) on register moves * Explicit revealing (secret → clear) operations * Type preservation during transitions This architectural design enables efficient MPC computation while maintaining the flexibility to support different protocols and optimization strategies. # Stoffel CLI Overview Source: https://docs.stoffelmpc.com/cli/overview Use the Stoffel CLI to create, check, build, run, and iterate on private-computation projects. The `stoffel` CLI is the entry point for building with Stoffel. Use it to create projects, check source, build bytecode, run clear checks, and exercise local MPC development flows. ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh ``` The installer writes `stoffel` to `~/.local/bin` by default. Add it to your path if your shell does not find it: ```bash theme={null} export PATH="$HOME/.local/bin:$PATH" ``` Check the available commands: ```bash theme={null} stoffel --help ``` ## Commands | Command | Alias | Purpose | | --------- | ----------------- | ------------------------------------------------------------------------------------ | | `init` | `new` | Create a new Stoffel project from a template. | | `check` | | Validate source and project MPC settings without writing bytecode. | | `compile` | | Write compiled bytecode for a project or source file; can also disassemble `.stflb`. | | `build` | | Build project bytecode under `target/`. | | `run` | `exec`, `execute` | Run a project directory, `.stfl` source file, or `.stflb` bytecode file. | | `dev` | | Watch a project and rerun it on local MPC when files change. | | `test` | | Run no-argument Stoffel test functions. | | `status` | `doctor` | Show project health and environment status. | | `clean` | | Remove generated build artifacts. | | `update` | `upgrade` | Check or update the CLI and project dependencies. | ## Create a project ```bash theme={null} stoffel init hello-mpc cd hello-mpc ``` Choose the template that matches your build path: ```bash theme={null} stoffel init hello-mpc # default Stoffel/Rust project stoffel init my-lib --lib # library-style Stoffel project stoffel init rust-app --template rust stoffel init py-app --template python stoffel init contract-app --template solidity-foundry stoffel init hardhat-app --template solidity-hardhat ``` `--force` writes template files into an existing directory without deleting unrelated files. ## Project structure The default template currently creates: ```text theme={null} hello-mpc/ ├── Cargo.toml ├── README.md ├── Stoffel.toml └── src/ ├── main.rs ├── main.stfl └── stoffel_bindings.rs ``` `Stoffel.toml` records package, MPC, and build settings: ```toml theme={null} [package] name = "hello-mpc" version = "0.1.0" [mpc] backend = "honeybadger" parties = 5 threshold = 1 [build] source = "src/main.stfl" target_dir = "target" ``` `backend` selects the MPC backend recorded in generated bytecode: | Value | Use when | | -------------- | ------------------------------------------------------------------ | | `honeybadger` | You want the default field-arithmetic MPC backend. | | `avss` | You want AVSS with the default curve. | | `avss:` | You want AVSS over a specific curve, for example `avss:secp256k1`. | For AVSS, you can also keep `backend = "avss"` and set `curve = "ed25519"`, `curve = "secp256k1"`, or another supported curve in the `[mpc]` table. See [MPC Protocols](../mpc-protocols/overview) for backend selection guidance. ## Check source Validate source and MPC settings without writing bytecode: ```bash theme={null} stoffel check ``` Use this before committing or before a longer local MPC run. ## Compile bytecode Compile a project or source file to Stoffel bytecode: ```bash theme={null} stoffel compile stoffel compile src/main.stfl --output target/debug/hello-mpc.stflb stoffel compile -O2 --backend honeybadger --parties 5 --threshold 1 stoffel compile -O2 --backend avss --field secp256k1 --parties 5 --threshold 1 ``` Disassemble an existing bytecode file: ```bash theme={null} stoffel compile --disassemble target/debug/hello-mpc.stflb ``` Useful compile flags: * `--output` / `--out`: write to a specific `.stflb` file when compiling one source file. * `--print-ir`: print compiler intermediate representation. * `-O`, `--opt-level`: set optimization level, for example `-O3`. * `--optimize`: use O2 unless `--release` selects O3. * `--release`: write under `target/release` and use O3 unless overridden. * `--backend`, `--field`, `--parties`, `--threshold`, `--instance-id`: override project MPC settings for this compile. Use `--backend honeybadger` for the default field-MPC backend, or `--backend avss --field ` / `--backend avss:` for AVSS. ## Build a project `build` writes bytecode under `target/` using project settings: ```bash theme={null} stoffel build stoffel build --release stoffel build -O2 ``` ## Run source or bytecode Run the current project: ```bash theme={null} stoffel run ``` Run a specific file: ```bash theme={null} stoffel run --program-info stoffel run target/debug/hello-mpc.stflb --entry main ``` Useful run flags: * `--entry`: function to execute; defaults to `main`. * `--input NAME=VALUE`: named function argument, repeat once per argument. * `--input-file FILE`: load named inputs from `.json`, `.csv`, or `.txt`. * `--client-input SLOT=VALUE`: local ClientStore input for programs that call `ClientStore.take_share`. * `--client-input-file FILE`: load ClientStore inputs from `.json`, `.csv`, or `.txt`. * `--expected-output-clients N`: declare local output-capable client slots `0..N-1`. * `--local`: run on the local MPC test network; this is the default unless `--network` or `--config` is set. * `--network --config FILE`: connect to an MPC network described by a network/off-chain client TOML file. * `--program-info`: print function and instruction metadata before executing. * `--timeout-secs N`: local MPC timeout; defaults to 180 seconds. ## Local MPC development Run the project once through the local MPC test network: ```bash theme={null} cd /path/to/hello-mpc stoffel dev \ --parties 5 \ --threshold 1 \ --once ``` Run in watch mode by omitting `--once`: ```bash theme={null} stoffel dev \ --parties 5 \ --threshold 1 ``` `stoffel dev` watches `Stoffel.toml` and the configured source tree, rebuilds, and reruns when a `.stfl` file or project config changes. Use `--poll-ms` to tune reload latency. ## Rust wrapper The default Rust template includes `src/main.rs` as an application wrapper. It imports `stoffel::prelude::*`, loads the generated binding metadata, compiles or loads the project program, sets local MPC topology with `parties` and `threshold`, and calls `.execute_local().await?` for local MPC testing. For application-style examples, prefer building bytecode with `stoffel build` and loading the resulting `.stflb` from Rust with `Stoffel::load_file("target/debug/.stflb")?`. ## Template status The Rust SDK path is the primary application workflow. Python and Solidity templates are useful for integration planning; use their generated README files and the current repository when validating a deployment. ## Troubleshooting ### The command accepts an option but my generated project fails Check the generated `README.md` and `src/main.stfl`. Template programs vary: some expect named function inputs with `--input`, while ClientStore examples expect local client inputs with `--client-input`. ### I need exact current flags Use command-specific help: ```bash theme={null} stoffel init --help stoffel compile --help stoffel run --help stoffel dev --help ``` # Docker Compose Network Source: https://docs.stoffelmpc.com/deployment/docker-local-network Run a multi-party Stoffel network with Docker Compose for local testing or deployment to your own backend environment. Use the Docker Compose stacks in the Stoffel repository when you need to run the networked runtime shape: coordinator, MPC parties, client input processes, node RPC addresses, identity material, backend selection, and persistent preprocessing or local stores. Compose is useful for local testing and for deploying a Stoffel MPC network to infrastructure you operate, including a developer-owned backend. The checked-in Compose files are examples, not app-ready deployment manifests: expect to read and heavily modify them for your application. For a first app loop, start with local MPC through the `stoffel` CLI or Rust SDK; move to Compose when you want explicit service topology, container boundaries, persistent stores, or backend integration. ## What the Docker stacks provide The Compose files make the runtime topology explicit and runnable: | Runtime piece | What to look for | | ----------------- | --------------------------------------------------------------------------------------------------------------------- | | Coordinator | A service that tracks the computation round, known parties, expected clients, output clients, and backend settings. | | Leader party | Party `0`, which also acts as the bootstrap point for peer discovery in the local Compose network. | | Other parties | Party processes with their own bind address, party ID, node certificate, key, and optional local/preprocessing store. | | Client processes | Containers that submit client input values to node RPC endpoints and, when configured, receive outputs. | | Program bytecode | A `.stflb` artifact mounted or packaged into the runtime image. | | Identity material | Node and client certificates/keys used by the coordinator and parties. | | Backend settings | Environment variables for HoneyBadger or AVSS and curve selection. | ## Source files From the Stoffel repository: | File | Use it for | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | [`Dockerfile`](https://github.com/Stoffel-Labs/stoffel/blob/main/Dockerfile) | Building the runtime image that packages `stoffel-run`, example bytecode, identity material, and the node entrypoint. | | [`docker/entrypoint.sh`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker/entrypoint.sh) | Understanding role-specific runtime flags for leader, party, bootnode, and client modes. | | [`docker/coordinator.Dockerfile`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker/coordinator.Dockerfile) | Building the off-chain coordinator wrapper image. | | [`docker-compose.yml`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker-compose.yml) | Five-party HoneyBadger run with coordinator and fixed local networking. | | [`docker-compose.coordinator.reserve-index.yml`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker-compose.coordinator.reserve-index.yml) | Coordinator, five parties, and two client containers using deterministic client index reservation. | | [`docker-compose.coordinator.reserve-index.preproc.yml`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker-compose.coordinator.reserve-index.preproc.yml) | Persistent preprocessing volumes layered on the reserve-index coordinator stack. | | [`docker-compose.avss.yml`](https://github.com/Stoffel-Labs/stoffel/blob/main/docker-compose.avss.yml) | Five-party AVSS/DKG-style local-store testbed and curve selection examples. | ## Swap and adapt a Compose stack Start by choosing the closest root Compose file, then copy it into your application or infrastructure repository before editing it. Do not patch the example in place and assume it now represents your app. The root files are runnable templates for Stoffel service roles; your app decides the bytecode, images, client services, network names, ports, identity material, storage, and backend settings. Use this adaptation order: 1. **Choose the baseline stack.** Use `docker-compose.yml` for a basic HoneyBadger coordinator/party run, `docker-compose.coordinator.reserve-index.yml` when your app has explicit client containers and client-slot assignment, add `docker-compose.coordinator.reserve-index.preproc.yml` when you need persistent preprocessing storage, or use `docker-compose.avss.yml` when you are exercising AVSS/local-store behavior. 2. **Copy the Compose file and overlays.** Keep the original root files as references. Edit an app-owned copy such as `compose.stoffel.yml` so your changes can live with the application deployment code. 3. **Replace the program artifact.** Build your application's `.stflb`, package or mount it into the runtime container, and update `STOFFEL_PROGRAM` to the in-container path. If the entrypoint changes, update `STOFFEL_ENTRY` too. 4. **Replace the image build.** Swap the example image or build context for the image that contains your app's bytecode, runtime wrapper, entrypoint script, certificates, and any app-specific client binaries. 5. **Rewrite client services.** Replace sample client containers and `STOFFEL_INPUTS` values with your own input/output client services. Match each service to the client slots and output recipients expected by your Stoffel program. 6. **Update network addresses.** Rename the Compose network, service names, coordinator address, party addresses, and `STOFFEL_SERVERS` entries so they match the hostnames your containers will actually resolve. Inside Compose, service names become DNS names; outside Compose, you may need routed hostnames, load balancers, or explicit published ports. 7. **Change ports deliberately.** Avoid copying host port mappings that collide with your backend or with multiple test networks on the same host. Keep container-internal ports aligned with service configuration, and change only the host-side published ports unless your runtime config also changes. 8. **Replace identities and secrets.** Generate deployment-specific node/client identity material and use secret management or protected environment files for tokens. Do not reuse checked-in local fixtures outside local tests. 9. **Update volumes and stores.** Replace example `ids/`, bytecode, preprocessing, and local-store mounts with app-owned directories or managed volumes. Decide which data must persist across restarts and which should be ephemeral. 10. **Select backend settings.** Set `STOFFEL_MPC_BACKEND`, `STOFFEL_MPC_CURVE`, `STOFFEL_N_PARTIES`, and `STOFFEL_THRESHOLD` to match the bytecode manifest, security target, and number of parties you are operating. 11. **Validate from the container network.** Run the adapted Compose stack, then check coordinator reachability, party-to-party reachability, client submission, output delivery, logs, and persistent store behavior from the same network namespaces the services use. The main swaps usually look like this: | Example field | Replace with | | ----------------------------------------- | ------------------------------------------------------------- | | Example `image:` or `build:` | Your runtime/client image or app-owned build context. | | `STOFFEL_PROGRAM=/app/programs/...` | Your packaged `.stflb` path inside the runtime container. | | Sample client service blocks | App-specific participant/output client services. | | `STOFFEL_INPUTS` fixtures | Runtime input wiring owned by your client service. | | Example service names and networks | Hostnames and networks used by your backend. | | Checked-in `ids/` mounts | Deployment-specific identity material. | | Example host ports | Ports that fit your backend, firewall, and local test matrix. | | Example preprocessing/local-store volumes | App-owned persistent volumes or managed storage. | For example, a private matchmaking app should not keep the sample client containers unchanged. It should package the matchmaking `.stflb`, run participant clients that submit matchmaking payloads to the reserved client slots, expose only the coordinator/node endpoints those clients need, and mount identity and store paths owned by that deployment. ## Switch from default programs to your program The root Docker image ships with example bytecode under `/app/programs/`. The Compose files select one of those defaults with `STOFFEL_PROGRAM`, such as `/app/programs/mpc_aes128_circuit.stflb` for the HoneyBadger stack or `/app/programs/avss_keygen.stflb` for the AVSS stack. To run your own app, the path in `STOFFEL_PROGRAM` must point to a `.stflb` file that exists inside every party container. Use one of these two patterns. ### Package the bytecode into an app image Use this for repeatable local testing, staging, or a developer-owned backend. 1. Build your app's bytecode with the same backend family you plan to run: ```bash theme={null} # From your Stoffel app project stoffel build --backend honeybadger --parties 5 --threshold 1 # Or, for an AVSS app, choose the AVSS backend and curve you need stoffel build --backend avss --field secp256k1 --parties 5 --threshold 1 ``` 2. Add an app-owned Dockerfile that starts from your Stoffel runtime base image or repeats the runtime stage from the root `Dockerfile`, then copies your bytecode into `/app/programs/`: ```dockerfile theme={null} FROM my-stoffel-runtime-base:latest COPY target/debug/my_app.stflb /app/programs/my_app.stflb ``` 3. Update your app-owned Compose file so every party uses that image and program path: ```yaml theme={null} x-party-common: &party-common image: my-stoffel-app-runtime:latest environment: &party-env STOFFEL_PROGRAM: /app/programs/my_app.stflb STOFFEL_ENTRY: main ``` 4. Keep the backend settings aligned with the bytecode. A HoneyBadger-built program should run with `STOFFEL_MPC_BACKEND=honeybadger`; an AVSS-built program should run with `STOFFEL_MPC_BACKEND=avss` and the same curve selection you built for. ### Mount the bytecode while iterating locally Use this only for local development loops where you want to rebuild bytecode without rebuilding the runtime image. ```yaml theme={null} x-party-common: &party-common volumes: - ./target/debug/my_app.stflb:/app/programs/my_app.stflb:ro - stoffel-local-store:/app/local-store environment: &party-env STOFFEL_PROGRAM: /app/programs/my_app.stflb STOFFEL_ENTRY: main ``` The left side of the mount is a host path; the right side is the in-container path. `STOFFEL_PROGRAM` must use the right-side path. If you run the stack from a different directory, update the host path or use an absolute path. Program switching is not only a filename change. If your program uses client inputs or sends outputs to clients, also update the client containers, `STOFFEL_INPUTS`, `STOFFEL_CLIENT_INDEX`, `STOFFEL_SERVERS`, output decoding, and any coordinator settings such as expected input/output clients. If the entry function is not `main`, set `STOFFEL_ENTRY` to the function you want every party to execute. ## Run a HoneyBadger Compose network Use `docker-compose.yml` when you want the HoneyBadger backend. The stack starts one coordinator and five MPC parties on the `stoffel-net` bridge. Each party receives `STOFFEL_MPC_BACKEND=honeybadger`, a party ID, coordinator address, node identity, RPC bind address, and the packaged program path. From a Stoffel source checkout, first render the Compose configuration you are about to run: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose -f docker-compose.yml config ``` Then start the HoneyBadger network: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose -f docker-compose.yml up --build ``` The default stack builds the runtime image, starts the coordinator, starts five party containers, and runs the packaged HoneyBadger program named by `STOFFEL_PROGRAM`. If you do not set `STOFFEL_PROGRAM`, the stack uses `/app/programs/mpc_aes128_circuit.stflb`. If you do not set `STOFFEL_MPC_BACKEND`, the stack uses `honeybadger`. To run your own HoneyBadger-compatible program, build or package your `.stflb` into the runtime image and point `STOFFEL_PROGRAM` at the in-container path: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ STOFFEL_PROGRAM=/app/programs/client_mul.stflb \ STOFFEL_MPC_BACKEND=honeybadger \ STOFFEL_MPC_CURVE=bls12-381 \ docker compose -f docker-compose.yml up --build ``` For an app-owned copy, keep the same backend shape and update the app-specific fields: ```yaml theme={null} services: party0: environment: STOFFEL_MPC_BACKEND: honeybadger STOFFEL_MPC_CURVE: bls12-381 STOFFEL_PROGRAM: /app/programs/my_app.stflb STOFFEL_ENTRY: main STOFFEL_N_PARTIES: "5" STOFFEL_THRESHOLD: "1" STOFFEL_COORD_ADDR: coordinator:31415 STOFFEL_RPC_ADDR: party0:16180 ``` Apply the corresponding changes to every party service: keep party IDs unique, update bind/RPC addresses consistently, and replace node identities with the identities for that deployment. Inspect the running HoneyBadger services: ```bash theme={null} docker compose -f docker-compose.yml ps docker compose -f docker-compose.yml logs -f coordinator party0 ``` Stop and remove the local containers and network when you are done: ```bash theme={null} docker compose -f docker-compose.yml down ``` ## Run the coordinator/client reserve-index stack Use this stack when you want to see the off-chain coordinator path with explicit client containers: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose -f docker-compose.coordinator.reserve-index.yml up --build ``` The stack contains: * one coordinator service on port `31415`; * five MPC party services; * node RPC endpoints on ports such as `16180` through `16184` inside the Compose network; * two client containers that submit inputs to deterministic client indices; * node and client certificates mounted from `ids/`. Swap client index assignment to check that the result follows the reserved index, not container start timing: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ STOFFEL_CLIENT0_INDEX=1 \ STOFFEL_CLIENT1_INDEX=0 \ docker compose -f docker-compose.coordinator.reserve-index.yml up --build ``` Add persistent preprocessing storage: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose \ -f docker-compose.coordinator.reserve-index.yml \ -f docker-compose.coordinator.reserve-index.preproc.yml \ up --build ``` ## Run an AVSS local-store stack Use `docker-compose.avss.yml` when you want the AVSS backend. This stack starts five party containers on the `stoffel-avss-net` bridge, sets `STOFFEL_MPC_BACKEND=avss` for every party, and mounts a separate persistent local store volume for each party at `/app/data`. From a Stoffel source checkout, first render the AVSS Compose configuration: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose -f docker-compose.avss.yml config ``` Then start the AVSS network: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ docker compose -f docker-compose.avss.yml up --build ``` The default AVSS stack runs `/app/programs/avss_keygen.stflb`. It leaves `STOFFEL_MPC_CURVE` empty unless you set it, so the runtime uses its default AVSS curve behavior. Select a curve or program with environment variables. For example, run the secp256k1 threshold-ECDSA fixture through AVSS: ```bash theme={null} STOFFEL_AUTH_TOKEN=replace-with-random-secret \ STOFFEL_MPC_CURVE=secp256k1 \ STOFFEL_PROGRAM=/app/programs/threshold_ecdsa_secp256k1.stflb \ docker compose -f docker-compose.avss.yml up --build ``` For an app-owned copy, keep the AVSS backend setting and update the program, curve, party addresses, and local-store mounts: ```yaml theme={null} services: party0: environment: - STOFFEL_MPC_BACKEND=avss - STOFFEL_MPC_CURVE=secp256k1 - STOFFEL_PROGRAM=/app/programs/my_avss_app.stflb - STOFFEL_ENTRY=main - STOFFEL_N_PARTIES=5 - STOFFEL_THRESHOLD=1 - STOFFEL_LOCAL_STORE=/app/data/local.redb volumes: - my-avss-party0-local:/app/data ``` Apply the same backend, curve, party count, and threshold to every AVSS party. Keep each party's `STOFFEL_PARTY_ID`, bind address, bootstrap address, and local-store volume distinct. Inspect the running AVSS parties and logs: ```bash theme={null} docker compose -f docker-compose.avss.yml ps docker compose -f docker-compose.avss.yml logs -f party0 ``` Stop the AVSS stack when you are done. Add `-v` only when you also want to remove the persistent local-store volumes: ```bash theme={null} docker compose -f docker-compose.avss.yml down docker compose -f docker-compose.avss.yml down -v ``` ## Environment variables to understand | Variable | Meaning | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `STOFFEL_AUTH_TOKEN` | Shared secret used for authenticated discovery/registration in these local stacks. Use a random value outside local demos. | | `STOFFEL_ROLE` | Runtime mode: leader, party, bootnode, or client. | | `STOFFEL_PARTY_ID` | Party index for an MPC node. | | `STOFFEL_N_PARTIES` | Total party count. | | `STOFFEL_THRESHOLD` | Fault/security threshold. | | `STOFFEL_PROGRAM` | Bytecode path inside the runtime container. | | `STOFFEL_ENTRY` | Entry function to execute. | | `STOFFEL_COORD_ADDR` | Off-chain coordinator address. | | `STOFFEL_RPC_ADDR` | Node RPC bind address used by clients. | | `STOFFEL_SERVERS` | Client-side list of node RPC endpoints. | | `STOFFEL_INPUTS` | Client input values submitted by a client container. | | `STOFFEL_CLIENT_INDEX` | Client slot/index claimed by a client container. | | `STOFFEL_CERT` / `STOFFEL_KEY` | Identity material for node or client mode. | | `STOFFEL_PREPROC_STORE` | Persistent preprocessing store path. | | `STOFFEL_LOCAL_STORE` | Persistent local store path. | | `STOFFEL_MPC_BACKEND` / `STOFFEL_MPC_CURVE` | Backend and curve selection. | ## Use with your own backend The Docker stacks are not only a local teaching aid. They are starting points that you adapt for a backend you control. For your own application, read the root Compose files and expect to heavily modify the Compose code and configuration rather than running the checked-in examples unchanged: build your app's `.stflb` bytecode, mount or package it into the runtime image, configure coordinator and party addresses for your environment, wire your own client input/output services, and connect your app or client services to the exposed coordinator/node RPC endpoints. Use Compose when you want to own the deployment shape yourself: * local laptop or CI testing with the same service roles you plan to operate; * a developer-owned backend where the coordinator, parties, and clients run as containers; * staging environments that need persistent preprocessing or local stores; * operator runbooks that should start from concrete services rather than SDK-local execution. The app-facing CLI and SDK workflows remain the quickest first loop. Compose shows and runs the services those workflows abstract away: * several MPC node processes; * a coordinator or bootstrap path; * configured parties and threshold; * client input submission; * bytecode execution; * explicit output reconstruction or delivery. For a deployed backend, replace local-only defaults with your own operational choices: real secrets, identity material, network addresses, TLS/routing, observability, storage, upgrade process, and access controls. At minimum, review and update the Compose files, Docker image build, entrypoint environment, program bytecode path, client containers, identities, exposed ports, volumes, and backend-specific settings for your application. Treat the repository stacks as runnable templates that show the service roles, not as drop-in production manifests for every app. ## Current caveats * These stacks are self-managed Compose deployments. They are suitable for local testing and developer-owned backend deployments, but they are not a managed hosting product. * Some coordinator paths are HoneyBadger/BLS12-381-specific while AVSS is exercised by a separate local-store stack. * The checked-in examples use fixed local subnets and local identity fixtures. Replace them with real identity, secret, network, and operations management when deploying outside a local test environment. * The runtime image packages selected example bytecode and sample client behavior. For your app, build your own `.stflb`, point `STOFFEL_PROGRAM` at it, and update the client/container code that submits inputs or receives outputs. ## Next steps * [MPC Backends](../mpc-protocols/overview) * [Rust SDK app integration](../rust-sdk/app-integration) * [CLI overview](../cli/overview) # Stoffel Developer Skills Source: https://docs.stoffelmpc.com/developer-skills/overview Agent-ready playbooks for building applications with the Stoffel framework. Stoffel Developer Skills are AI-agent-ready playbooks for developers building applications with the Stoffel framework. They are designed to help coding agents and humans choose the right Stoffel workflow, use current CLI and SDK APIs, run local MPC smoke tests, generate typed client IO bindings, prepare deployment-shaped network handoffs, and debug common app-level failures. These skills are app-development guides, not internal maintainer guides. They focus on creating and validating Stoffel applications with public tooling. ## Multi-user application invariant For client-owned private input, each input owner's device or process is a distinct participant MPC client. It submits directly through the Stoffel client protocol to the separately deployed MPC network. The application control plane handles public metadata, authorization, session/bootstrap configuration, non-sensitive receipts, lifecycle, and explicitly authorized opened aggregates; it must not receive or persist participant plaintext. A backend gateway that sees raw input is a distinct, weaker trust model and requires explicit approval. For rooms, voting, auctions, private matching, prediction markets, federated analytics, or other multi-user apps, begin with [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path), then apply [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration). Use the main docs for canonical CLI, SDK, language, and deployment behavior. Use these skills as the agent execution layer: they tell an AI coding agent which docs to consult, what order to work in, what commands to run, and what evidence to collect before reporting that a task is done. ## Use with AI agents Mintlify serves these skills through standard skill discovery endpoints and a hosted search MCP server. Use both when possible: * Skills tell an agent how to build with Stoffel. * MCP gives the agent live read/search access to the current docs. ### Install the Stoffel skills Install the published Stoffel skills with the `skills` CLI: ```sh theme={null} npx skills add https://docs.stoffelmpc.com ``` Useful variants: ```sh theme={null} # List available skills without installing npx skills add https://docs.stoffelmpc.com --list # Install all Stoffel skills without prompts npx skills add https://docs.stoffelmpc.com --all # Install globally for supported local agents npx skills add https://docs.stoffelmpc.com --global --all # Install one focused skill npx skills add https://docs.stoffelmpc.com --skill stoffel-full-app-golden-path # Install for a specific supported agent npx skills add https://docs.stoffelmpc.com --agent claude-code --skill stoffel-full-app-golden-path ``` ### Connect the Stoffel docs MCP server Mintlify hosts the Stoffel search MCP server at: ```txt theme={null} https://docs.stoffelmpc.com/mcp ``` Use the MCP server when an agent needs to search the docs, read full pages as Markdown, or discover the hosted `skill.md` files as MCP resources. Install it into supported local agents with `add-mcp`: ```sh theme={null} npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp ``` ### Agent harness setup Use the generic setup prompt from [Installation](/getting-started/installation#install-stoffel-with-an-ai-coding-agent) first. If your agent needs explicit MCP configuration, add the same Stoffel docs MCP URL through that harness's MCP setup path. | Agent harness | Start here | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hermes Agent | Add an HTTP MCP server under `mcp_servers` in Hermes config, for example `stoffel_docs: { url: "https://docs.stoffelmpc.com/mcp" }`, then run `/reload-mcp` or restart Hermes. | | Claude Code | `claude mcp add --transport http stoffel-docs https://docs.stoffelmpc.com/mcp` | | Cursor | Add `https://docs.stoffelmpc.com/mcp` in Cursor's MCP server settings. | | Codex CLI | Use `npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp` if your local Codex setup supports MCP helpers; otherwise add the same URL in its MCP config. | | Gemini CLI | Use `npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp` if your local Gemini setup supports MCP helpers; otherwise add the same URL in its MCP config. | | Windsurf | Add `https://docs.stoffelmpc.com/mcp` in Windsurf's MCP server settings. | After setup, ask the agent to prove it can search the Stoffel docs before asking it to edit code. After connection, the MCP server exposes read-only tools for searching Stoffel docs and querying the docs filesystem. Tool names are client-specific, but they correspond to: * `search_stoffel_documentation` * `query_docs_filesystem_stoffel_documentation` ### Discovery endpoints Agents can discover skills and MCP metadata programmatically: * `https://docs.stoffelmpc.com/skill.md` * `https://docs.stoffelmpc.com/.well-known/agent-skills/index.json` * `https://docs.stoffelmpc.com/.well-known/skills/index.json` * `https://docs.stoffelmpc.com/.well-known/mcp` * `https://docs.stoffelmpc.com/.well-known/mcp.json` * `https://docs.stoffelmpc.com/.well-known/mcp/server-card.json` * `https://docs.stoffelmpc.com/.well-known/mcp/server-cards.json` ### Verify agent access ```sh theme={null} # Skills discovery npx skills add https://docs.stoffelmpc.com --list # MCP discovery document curl https://docs.stoffelmpc.com/.well-known/mcp # MCP server card with advertised tools curl https://docs.stoffelmpc.com/.well-known/mcp/server-card.json ``` After MCP is connected, ask the agent to search the Stoffel docs for `ClientStore` or `local MPC` and summarize the relevant command. That verifies the agent can use live docs, not just the installed skill text. ## Choose the first skill | If the user asks for... | Start with | | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Toolchain setup or a first runnable project | [Stoffel App Getting Started](/developer-skills/stoffel-app-getting-started) | | A complete custom application, room, auction, vote, private match, or other multi-user app | [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path), then [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) | | CLI commands, project config, inputs, local runs, or bytecode inspection | [Stoffel CLI App Workflow](/developer-skills/stoffel-cli-app-workflow) | | StoffelLang syntax, functions, types, or examples | [Stoffel-Lang App Programming](/developer-skills/stoffel-lang-app-programming) | | Multi-user client-owned private inputs or ClientStore app architecture | [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path), then [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) | | StoffelLang secret types, secret arithmetic, `ClientStore` program semantics, or client outputs | [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) | | Rust host integration, local SDK execution, clients, or servers | [Stoffel Rust App SDK](/developer-skills/stoffel-rust-app-sdk) | | Generated Rust input/output types | [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings) | | Local MPC debugging or hot reload | [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) | | Network/off-chain integration | [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) | | Deployment artifacts and operator handoff | [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) | | A failing app or unclear error | [Stoffel App Troubleshooting](/developer-skills/stoffel-app-troubleshooting) | For runnable app tasks, the agent should return command output from each changed layer. A code diff without `stoffel check`, `stoffel build`, and a local MPC run is not enough. ## Skills * [Stoffel App Getting Started](/developer-skills/stoffel-app-getting-started) — Install the Stoffel tooling, create a new app, run first local smoke tests, and choose the right development path. * [Stoffel CLI App Workflow](/developer-skills/stoffel-cli-app-workflow) — Use the stoffel CLI to init, check, build, compile, run, test, inspect, and troubleshoot Stoffel apps. * [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path) — Build a complete app from privacy boundary through StoffelLang, Rust SDK integration, local MPC validation, typed bindings, and deployment handoff. * [Stoffel-Lang App Programming](/developer-skills/stoffel-lang-app-programming) — Write .stfl application logic using supported Stoffel-Lang syntax, types, builtins, and example patterns. * [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) — Build MPC apps with secret types, Share, ClientStore, Mpc, MpcOutput, and runnable private-input examples. * [Stoffel Rust App SDK](/developer-skills/stoffel-rust-app-sdk) — Embed Stoffel in Rust apps using the SDK for compilation, bytecode loading, local execution, clients, and servers. * [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings) — Generate and use Rust typed client input/output bindings from exact Stoffel bytecode manifests. * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) — Run local MPC smoke tests, ClientStore input flows, hot reload, and SDK local coordinator-backed execution. * [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) — Move from local bytecode to client/server builders, network config, and off-chain coordinator integration. * [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) — Prepare production-shaped artifacts, topology, coordinator settings, identity material, client config, and operational verification. * [Stoffel App Troubleshooting](/developer-skills/stoffel-app-troubleshooting) — Diagnose app-level init, check, build, run, local MPC, binding, SDK, and network failures with evidence. * [Stoffel AI Agent Implementation](/developer-skills/stoffel-ai-agent-implementation) — Give AI coding agents backend, input/output, validation, and cost-model context before they write Stoffel code. ## Source and maintenance The visible pages are mirrored into `.mintlify/skills//SKILL.md` so developers can read them in the docs and AI agents can fetch them as skill resources. After editing a page in `developer-skills/`, run `python scripts/sync_developer_skills.py` before validating. # Stoffel AI Agent Implementation Source: https://docs.stoffelmpc.com/developer-skills/stoffel-ai-agent-implementation Give AI coding agents the context they need to build and validate Stoffel applications correctly. Use this skill when asking an AI coding agent to build, modify, deploy, or debug a Stoffel application. The goal is to give the agent enough context to choose the right MPC backend, model secret inputs correctly, and validate the smallest working program before expanding the app. ## Give the agent Stoffel docs access Before a large implementation task, install the Stoffel skills and connect the live docs MCP server so the agent can use current docs instead of training-data guesses. Install the skills: ```sh theme={null} npx skills add https://docs.stoffelmpc.com ``` List the published skills first if you need to choose a narrower playbook: ```sh theme={null} npx skills add https://docs.stoffelmpc.com --list ``` Connect the Mintlify-hosted search MCP server: ```sh theme={null} npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp ``` For Claude Code directly: ```sh theme={null} claude mcp add --transport http stoffel-docs https://docs.stoffelmpc.com/mcp ``` Programmatic discovery endpoints: * `https://docs.stoffelmpc.com/.well-known/agent-skills/index.json` * `https://docs.stoffelmpc.com/.well-known/mcp` * `https://docs.stoffelmpc.com/.well-known/mcp/server-card.json` ## Mandatory portability contract Before changing files or dependencies, the agent must: 1. Discover and confirm the project root from its current working directory and repository markers such as `Stoffel.toml`, `Cargo.toml`, or `.git`. It must not invent or require a machine-specific path such as `/workspace/...`. 2. Select Stoffel and related project dependencies from public, reproducible sources in this order: the current crates.io release, then the official GitHub repository pinned to a full immutable commit SHA when the needed change is not published. 3. Never use a floating branch or require a local path, sibling checkout, or other external filesystem checkout in the default app implementation. 4. Use a local Stoffel checkout only when the user explicitly requests framework development. Mark that route **nonportable** and keep it separate from the default public-dependency implementation. 5. Stop and report the unavailable dependency and attempted public sources if no suitable public dependency exists. Never silently fall back to a local checkout. Keep the implementation prompt version-agnostic. Concrete versions belong in installation docs or the generated project manifest. ## Start with the application trust architecture For client-owned private input, the input owner's device or process is the Stoffel MPC client. It submits directly through the client protocol to the separately deployed MPC service. An application backend may manage public metadata, authorization, session configuration, non-sensitive receipts, lifecycle, and explicitly authorized opened aggregates, but it must not receive or persist participant plaintext. A backend gateway that receives raw input is a separate, weaker trust model. Do not introduce it implicitly or use it to work around missing browser/client support. Before asking an agent to write code, require it to answer: 1. Which values are secret and public. 2. Who owns each plaintext input and where that plaintext exists before protection. 3. Which participant-owned process executes the Stoffel client protocol. 4. Which components are forbidden from receiving, logging, queueing, caching, analyzing, or persisting plaintext. 5. What the application control plane may receive and persist. 6. Who receives each output. 7. Whether each output is an opened value, client-output share, public commitment, curve-encoded value, or signature-related artifact. 8. Whether the requested participant runtime supports direct client-protocol submission. 9. Which backend is selected and why. 10. Which command proves the program is valid. 11. Which command proves local MPC works. 12. Which command proves the production private-data path bypasses the application service. 13. Which artifacts and configs are required before deployment. 14. Which discovered project root the agent will modify. 15. Which public dependency source satisfies the portability contract. 16. Whether framework development was explicitly requested; otherwise local checkout use is forbidden. Do not begin implementation while the plaintext location, submission process, forbidden components, persistence allowlist, output recipients, participant runtime support, or dependency source is unresolved. For source snippets, the validation command is usually: ```bash theme={null} stoffel check path/to/program.stfl ``` For docs changes, also run: ```bash theme={null} npx mintlify validate npx mintlify broken-links ``` ## Backend selection context Give the agent the backend decision in terms of value representation and output boundary. Use HoneyBadgerMPC when: * the program computes over secret integers, fixed-point values, or field-compatible shares; * the main cost questions are multiplication count, multiplication depth, comparisons, and reveal boundaries; * public parameters can stay public while private inputs remain ordinary MPC shares; * the outside system consumes an opened result or client-output shares from private computation. Use AVSS when: * the program needs public commitments to secret shares; * the backend must use a curve that matches an external verifier or protocol; * public transcript bytes must become curve-field challenges; * the outside system consumes a commitment, curve-encoded value, opened scalar response, or signature-related artifact. If the task is ordinary private arithmetic over application values, start with HoneyBadgerMPC. Use AVSS only when the protocol needs committed scalar shares, curve compatibility, or threshold-cryptography artifacts. ## Prompt template ```text theme={null} Build the smallest StoffelLang program for this task before expanding it. Secret values: - ... Public values: - ... Input owners and plaintext locations: - client 0 owns ...; plaintext exists in ... - client 1 owns ...; plaintext exists in ... Participant client path: - participant runtime: native Rust / participant-side Tauri Rust / supported browser client / other - process that loads bindings and submits each client slot: ... - separately deployed coordinator/MPC endpoints: ... Application control plane: - allowed public metadata/session fields: ... - persistence allowlist: ... - non-sensitive receipt shape: ... - components forbidden from plaintext: API, logs, traces, database, cache, queue, analytics, crash reports, ... Runtime capability gate: - direct client protocol support: verified / unsupported / unverified - if unsupported or unverified: stop or choose an explicitly participant-controlled sidecar; do not add a plaintext backend gateway Output boundary: - opened value / client-output share / public commitment / curve-encoded artifact / signature-related artifact Backend: - honeybadger or avss: - reason for choosing it Project and dependency portability: - discovered project root and the repository markers used to confirm it - dependency source: current crates.io release / official GitHub repository at a full immutable commit SHA - public-source availability confirmed: yes/no - framework-development exception explicitly requested: yes/no - if yes, describe the separate nonportable local-checkout route; if no, do not use local or sibling path dependencies - if no suitable public source is available, stop and report the attempted sources Cost and safety constraints: - keep public constants, transcript bytes, hashes, and encodings public until a secret operation needs them - avoid secret × secret multiplication unless required - use public weights/constants when they are not private inputs - minimize openings and reveal only the intended output boundary - for AVSS signing flows, never persist or reuse per-signature nonces unless the protocol explicitly requires and protects that state Validation: - run `stoffel check ...` - run `stoffel build --program-info` - run a local MPC smoke with documented inputs and label it as a trusted fixture harness - verify control-plane schemas and persistence contain no participant private fields or generic private payloads - run a plaintext-canary check across service requests, logs, traces, database, cache, queues, analytics, crash reports, and receipts - verify participant clients submit directly to the separately deployed MPC service - if deployment is requested, produce bytecode, bindings, topology, coordinator/client config, and operator handoff fields - if editing docs, run `python scripts/sync_developer_skills.py`, `npx mintlify validate`, and `npx mintlify broken-links` ``` ## HoneyBadgerMPC agent guidance Ask the agent to inspect circuit shape before optimizing code: * Count secret × secret multiplications. * Separate multiplication count from multiplication depth. * Keep public weights, thresholds, normalization factors, and lookup tables public when possible. * Treat comparisons, bit decomposition, and nonlinear functions as expensive until measured. * Use `MpcOutput` when a client should receive result shares instead of opening the result to the host application. * Size preprocessing for the largest expected input shape if the program runs on a network deployment. Useful APIs and concepts: * `ClientStore.get_number_clients()` * `ClientStore.take_share(...)` * `ClientStore.take_share_fixed(...)` * `Mpc.has_capability("client-output")` * `MpcOutput.send_to_client(...)` * `Share.add`, `Share.mul`, `Share.mul_scalar` * opened values via `.reveal()`, `Share.open(...)`, or `open_fixed()` where appropriate ## AVSS agent guidance Ask the agent to keep the cryptographic transcript explicit: * Choose the curve based on the external verifier or protocol. * Keep transcript bytes public until they become a scalar challenge with `Crypto.hash_to_field(..., Mpc.curve())`. * Use `get_commitment(0)` when the outside system needs a public commitment or public key point. * Persist long-lived key shares with `LocalStorage` when the workflow requires stable key material. * Do not persist or reuse per-signature nonces unless the protocol explicitly requires and protects that state. * Minimize openings; open scalar responses only when the protocol boundary requires it. Useful APIs and concepts: * `Share.random()` * `LocalStorage.exists(...)` * `LocalStorage.store(...)` * `LocalStorage.load(...)` * `LocalStorage.load_share(...)` * `secret_key.get_commitment(0)` * `Mpc.curve()` * `Crypto.hash_to_field(...)` * `Crypto.point_to_sec1(...)` * `MpcOutput.send_to_client(...)` ## Common corrections to give the agent * Do not treat `client`, `app`, `backend`, `gateway`, and participant-side Tauri Rust as interchangeable roles. * Do not send participant plaintext through an application endpoint in the default client-owned-input architecture; a gateway that does this is a separately approved degraded-trust design. * Do not use local `.with_client_input(...)` fixture injection as production private-data-plane evidence. * Do not compensate for unsupported browser/WASM submission by silently adding a plaintext backend gateway. * Do not use AVSS for generic private arithmetic unless the task needs commitments or curve-compatible artifacts. * Do not reveal intermediate secret values just to make the program easier to write. * Do not turn public transcript material into secret shares earlier than necessary. * Do not assume a curve selector applies to HoneyBadgerMPC. * Do not stop after writing code; run the relevant validation command and report the real output. ## See also * [MPC Backends](/mpc-protocols/overview) * [Performance and Circuit Shaping](/mpc-protocols/performance-and-circuit-shaping) * [HoneyBadgerMPC](/mpc-protocols/honeybadger-mpc) * [AVSS](/mpc-protocols/avss) * [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path) * [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) * [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) # Stoffel App Getting Started Source: https://docs.stoffelmpc.com/developer-skills/stoffel-app-getting-started Install Stoffel, create your first app, run local checks, and choose the build path your product needs. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use portable public dependencies by default. A local checkout is a separate, nonportable framework-development workflow and is allowed only when explicitly requested. ## Use when Use this playbook when a developer or coding agent needs the shortest path from an empty directory to a working Stoffel app. ## Current source of truth Use the public docs when available, then verify against the current app-facing repo surfaces: * `README.md` * `crates/stoffel-cli/src/main.rs` * `crates/stoffel-cli/src/project.rs` * `crates/stoffel-lang/examples/README.md` * `crates/stoffel-rust-sdk/README.md` These are source-inspection references for app behavior, not instructions for app developers to edit framework internals. ## Mandatory portability contract Apply these rules before installing dependencies or creating files: 1. Discover and confirm the project root from the current working directory and repository markers such as `Stoffel.toml`, `Cargo.toml`, or `.git`. Do not invent or require a machine-specific path such as `/workspace/...`. 2. Resolve Stoffel and related project dependencies from public, reproducible sources in this order: the current crates.io release, then the official GitHub repository pinned to a full immutable commit SHA when the needed change is not published. 3. Never use a floating branch or require a local path, sibling checkout, or other external filesystem checkout for the default app path. 4. Use a local Stoffel checkout only when the user explicitly requests framework development. Label that workflow **nonportable** and keep it separate from the default instructions below. 5. If no suitable public dependency is available, stop and report the missing dependency and attempted public sources. Do not silently replace it with a local path. This playbook stays version-agnostic. Obtain concrete versions from the installation docs and record them in the app's dependency manifest. ## Prerequisites * Rust stable and Cargo. * The `stoffel` CLI from the documented installation path. * Crates.io dependencies for Rust SDK work. ## Install Install the CLI: ```sh theme={null} curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" stoffel --help ``` ## Create the first app ```sh theme={null} stoffel init my-stoffel-app cd my-stoffel-app stoffel status --verbose stoffel check stoffel build stoffel run --timeout-secs 180 ``` The default project template includes a Rust wrapper as well as `.stfl` source. Run the wrapper too: ```sh theme={null} cargo build --locked cargo run --locked ``` ## Know the app shape A new app normally includes: * `Stoffel.toml`: app metadata, default source path, output target dir, and local MPC defaults. * `src/main.stfl`: the Stoffel program. * `target/debug/*.stflb`: compiled bytecode after `stoffel build`. * Optional wrapper files (`Cargo.toml`, `src/main.rs`, `src/stoffel_bindings.rs`) when using the default or Rust templates. `Stoffel.toml` is a project/build config. It is not the network/off-chain client config passed to `stoffel run --network --config`. ## Choose a development path For client-owned private input in a multi-user or networked application, first complete the trust architecture in [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path). Each participant-owned client should submit directly to the separately deployed MPC service; the application control plane must not receive or persist plaintext. Then use [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) for the direct client path and runtime capability gate. * CLI-only path: mostly `.stfl` source and trusted local smoke tests. * Rust SDK path: embedding compilation/execution, creating clients/servers, generating typed client IO bindings, or integrating with a Rust service. * Local MPC path: private workflows that need real local party execution before network/off-chain work; local fixture injection is not production private-data-plane evidence. * Network/off-chain path: advanced client/server/coordinator integration after the local smoke passes. ## Fast examples to inspect * At the full immutable official GitHub commit selected by the portability contract, inspect clear language basics under `crates/stoffel-lang/examples/local_control_flow`, `local_collections`, and `local_text_processing`. * Inspect the first private input flow at `crates/stoffel-lang/examples/mpc_client_private_score` in that same official source revision. * Inspect the ClientStore gallery under `crates/stoffel-lang/examples/bits/secret/*`, `matrix/secret/*`, `polynomials/secret/*`, `number_theory/secret/*`, and the app-level `mpc_*` algorithm examples in that revision. Many secret examples now include a first-line `# run-args:` header. Copy those flags when running the example locally. ## Validation / done criteria A first-app task is complete only when real output has been collected from: ```sh theme={null} stoffel status --verbose stoffel check stoffel build stoffel run --timeout-secs 180 ``` For Rust wrapper apps, also collect output from: ```sh theme={null} cargo build --locked cargo run --locked ``` For a secret ClientStore example, include its documented `# run-args:` flags and `--expected-output-clients` if present. ## Common pitfalls * For app development, use the public dependency precedence in the portability contract; do not recommend a local path dependency. * Do not confuse browsing examples in an official source revision with requiring that repository as a sibling checkout. * Do not describe `Stoffel VM` internals unless they explain public app behavior. * Do not claim local MPC works until a real run has completed. * Do not treat `Stoffel.toml` as network/off-chain config. * Do not omit `--expected-output-clients` when running examples that send outputs to clients. ## Next playbooks * [Stoffel CLI App Workflow](/developer-skills/stoffel-cli-app-workflow) * [Stoffel-Lang App Programming](/developer-skills/stoffel-lang-app-programming) * [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) * [Stoffel Rust App SDK](/developer-skills/stoffel-rust-app-sdk) # Stoffel App Network and Off-Chain Integration Source: https://docs.stoffelmpc.com/developer-skills/stoffel-app-network-and-offchain-integration Move from local bytecode to client/server builders, network config, and off-chain coordinator integration. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use current public crates.io releases by default, then the official GitHub repository at a full immutable revision when the needed change is not published. A local checkout is a separate, explicitly requested, nonportable framework-development workflow. ## Use when Use this playbook when an app moves beyond local runs and needs client/server builders, network config, off-chain coordinator integration, or typed ClientStore IO against real nodes. ## Goal Guide advanced app developers from local bytecode to app-level network/off-chain integration using public SDK builders, while labeling lower-layer behavior with the current component status. For client-owned private input, the participant-owned process is the Stoffel MPC client. It submits directly to the separately deployed MPC service. The application control plane may issue public session configuration and receive non-sensitive receipts or explicitly authorized opened aggregates, but it must not receive or persist participant plaintext. ## Separate the application roles Do not combine these roles into one `client/app` layer: | Role | Responsibility | Plaintext boundary | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | Application control plane | Public metadata, authentication, session lifecycle, client-slot/capability assignment, network discovery, non-sensitive receipts, authorized aggregates | Must not receive participant private input | | Participant MPC client | Loads pinned bindings/config, validates its owner's input, submits its assigned slot, decodes authorized output | May see only its owner's plaintext | | MPC service plane | Separately deployed coordinator and long-running parties | Receives client-protocol material according to the deployment, not application-service plaintext | | Output recipient | Participant client or application service named by the privacy worksheet | Receives only explicitly authorized output | A backend gateway that accepts raw input is a distinct, weaker trust model. Name it and require explicit approval; do not introduce it to work around missing participant-runtime support. ## Current source of truth * `crates/stoffel-rust-sdk/README.md` * `crates/stoffel-rust-sdk/src/runtime.rs` * `crates/stoffel-rust-sdk/src/config.rs` * `crates/stoffel-rust-sdk/src/client.rs` * `crates/stoffel-rust-sdk/src/server.rs` * `crates/stoffel-rust-sdk/src/coordinator/offchain.rs` * `crates/stoffel-rust-sdk/examples/network_config.rs` * `crates/stoffel-rust-sdk/examples/client_server.rs` ## Preconditions Before network integration, complete the trust-boundary worksheet in [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path). Identify the participant runtime, the process that executes client submission, components forbidden from plaintext, the control-plane persistence allowlist, and output recipients. Then verify local program behavior: ```sh theme={null} stoffel status --verbose stoffel check stoffel build --program-info stoffel run --timeout-secs 180 ``` If the app uses typed client IO, generate bindings from the exact bytecode first. See [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings). ## Portability and provenance preflight Before interpreting a network result, label where every command ran. Keep these contexts distinct in notes and logs: * **app checkout**: the consumer application repository; * **framework checkout**: a Stoffel source tree, only when intentionally used; * **deployment host**: the machine that runs a coordinator, party, or client service; * **container**: the image plus the mounts and network namespace visible inside it; * **CI**: the runner image and job that reproduce the consumer workflow. Record a provenance manifest before building or connecting: | Evidence | Record | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | App repository | canonical repository URL, commit, branch/tag if relevant, and clean/dirty state | | Framework repository, if used | canonical repository URL, commit, and clean/dirty state | | CLI source | how `stoffel` was installed and the resolved executable path; record its reported version/build identity when available | | SDK source | registry/package version or git URL plus revision; if a path dependency is intentional, record its canonical path | | Dependency resolution | committed lockfile and its hash, or an explicit reason no lockfile applies | | Program artifacts | bytecode hash and generated binding/manifest hashes from the same build | | Container, if used | immutable image digest, not only a mutable tag | Do not treat a framework-checkout example as consumer proof. The portability gate is a **clean external checkout** of the app, outside the framework repository, using only documented public CLI and SDK dependencies. It must build and run without uncommitted files, undeclared path dependencies, workspace inheritance from the framework repository, or framework source mounted into a container. Record the external checkout path, repository URL, commit, clean status, dependency source, commands, and result. If this gate was not run, classify the result as **not clean-room tested** rather than portable. For a public dependency consumer proof, create or use the smallest app that imports the publicly documented SDK/CLI source, resolves from its committed lockfile, builds bytecode and bindings, and exercises the same client-facing path. A successful framework workspace test is useful framework validation, but is not this proof. ### Containers and addresses * Mount only declared app inputs, generated deployment bundles, state, and identity files. Do not mount a framework checkout, a developer package cache containing unpublished builds, or a host `target/` directory into the proof run. * Record host-to-container mount mappings and verify the bytecode/binding hashes inside the container after mounting. Use read-only mounts for release artifacts and config where practical. * Do not copy `127.0.0.1` or `localhost` across host/container boundaries: loopback names the current network namespace. Record bind addresses separately from advertised/reachable coordinator, mesh, and RPC addresses. * Bind services deliberately, publish only required ports, and test reachability from the actual peer/client context rather than only from the host. * Pin images by digest and include that digest in the result. ## Runtime builders The SDK runtime exposes app-level builders: ```rust theme={null} let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = stoffel::Stoffel::load_file(app_root.join("program.stflb"))?.build()?; let client = runtime.client(); let server0 = runtime.server(0); let deployment_client = runtime.client_for_deployment(&deployment); let server_for_config = runtime.server_for_config(&config); let servers = runtime.servers_for_deployment(&deployment); ``` For ClientStore apps: ```rust theme={null} let client_config = runtime.offchain_client_config(0)?; ``` Callers still provide coordinator address, node RPC addresses, timestamp, and client identity material explicitly. ## Network config concepts App-level network config must align on: * party id * bind addresses and server addresses * expected parties * expected clients * threshold * backend and curve * preprocessing sizes when required by the selected backend * deployment-level mapping of party configs * client slot and ClientStore IO shape Use SDK builders so validation fails early: ```rust theme={null} let config = NetworkConfig::builder() .party_id(0) .bind_address("127.0.0.1:19200") .expected_parties(5) .expected_clients(1) .peers([ (1, "127.0.0.1:19201"), (2, "127.0.0.1:19202"), (3, "127.0.0.1:19203"), (4, "127.0.0.1:19204"), ]) .threshold(1) .honeybadger() .consensus_timeout(std::time::Duration::from_secs(60)) .preprocessing(1000, 500) .build()?; config.validate_server_addresses()?; ``` ## Off-chain ClientStore flow 1. Compile/build app bytecode. 2. Run local MPC with the same ClientStore inputs and expected output clients. 3. Generate typed bindings from that bytecode. 4. Build runtime from bytecode and generated manifest. 5. Derive off-chain client config for a client slot. 6. Attach coordinator address, node endpoints/RPC addresses, timestamp, and client identity material. 7. Configure the separately deployed MPC service layer with the same bytecode, topology, backend, and client/output slots. 8. Have each participant-owned client submit its own typed input directly to that deployment. 9. Reconcile only non-sensitive submission status with the application control plane. 10. Deliver typed outputs only to recipients authorized by the privacy worksheet. 11. Validate typed outputs and consensus/order evidence where applicable. The SDK can validate and carry the app-level config, but live network deployment also needs operator-owned process supervision, identity files, node RPC reachability, and persistence/state decisions. Use [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) for that handoff. ### Client input ownership boundary * Each SDK client owns its private `ClientStore` values and submits its complete typed input vector through the client protocol. * The generated manifest plus client configuration bind a client slot to that vector's ordered input shape. * An application server may manage public lifecycle, authorization, and bootstrap metadata, but it must not receive or proxy plaintext private inputs. * Coordinator and party services may validate value-blind session, identity, slot, range, and topology metadata and process protocol messages. That does not make them application-level input owners. * Do not invent server-builder APIs for participant values. If a client transport is missing, implement or fix the SDK client transport instead of moving input ownership to the server. ## Control-plane bootstrap and receipts A control plane may return public session configuration such as: ```json theme={null} { "sessionId": "session_123", "clientSlot": 1, "programHash": "sha256:...", "inputSchemaId": "prediction-v1", "coordinatorEndpoint": "https://coordinator.example.com", "nodeRpcEndpoints": ["https://node-0.example.com"], "deploymentEpoch": 42, "submissionCapability": "short-lived-signed-token" } ``` A non-sensitive receipt may contain: ```json theme={null} { "sessionId": "session_123", "clientSlot": 1, "submissionId": "sub_456", "status": "accepted", "receivedAt": "..." } ``` Do not place participant predictions, typed private inputs, reversible encodings, generic private payload blobs, secret-sharing randomness, or participant shares in control-plane APIs, persistence, logs, queues, analytics, or receipts. ## Participant runtime capability gate Resolve this before implementation: | Participant runtime | Required decision | | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | | Native or Rust client, including participant-side Tauri Rust | Use direct participant-to-MPC submission when supported by the current SDK | | Browser/WASM with a supported Stoffel client package | Submit directly from the participant client | | Browser/WASM without direct support | Stop at the capability gap or use an explicitly participant-controlled sidecar; do not proxy plaintext through the backend | | Backend gateway | Degraded trust: the gateway sees raw input and requires explicit approval | | Local CLI or fixture harness | Development evidence only; not production private-data-plane evidence | ## CLI network execution The CLI can execute against a network config: ```sh theme={null} stoffel run target/debug/app.stflb --network --config path/to/network-client.toml --client-id 0 ``` Important: `--config` is network/off-chain client config, not app `Stoffel.toml`. ## Validation / done criteria * Local smoke test passes first. * Bytecode hash and generated bindings are recorded together. * Coordinator address, node mesh addresses, node RPC addresses, identity material, and expected client certificates are explicitly configured or listed as operator handoff fields. * Network config validates before starting servers/clients. * Client IO metadata matches generated bindings. * Real participant-client/network run returns expected output or a concrete error with logs. * Control-plane schemas, persistence, logs, and receipts contain no participant plaintext. * A plaintext canary test confirms private input bypasses application-service requests, storage, queues, caches, traces, analytics, and crash reports. * The participant runtime has verified direct client-protocol support or an explicit capability blocker/participant-controlled sidecar decision. * Every output recipient matches the privacy worksheet. * Any coordinator/network assumptions are labeled with current component status and paired with deployment validation guidance. * The clean external checkout/public dependency consumer proof passes, or the result is explicitly labeled **not clean-room tested**. * The provenance manifest identifies every execution context, dependency source, lockfile, artifact hash, and applicable image digest. * Services and the client consume the recorded bytecode/binding bundle unchanged; hashes are checked at build, service startup, and client execution boundaries. Framework validation: ```sh theme={null} cargo test --locked -p stoffel-rust-sdk cargo run --locked -p stoffel-rust-sdk --example network_config cargo run --locked -p stoffel-rust-sdk --example client_server ``` These framework-checkout commands do not replace the external consumer proof. In CI, use separate jobs or clearly labeled steps for: 1. app-checkout check/build/local smoke; 2. clean external public-dependency consumer build; 3. bytecode/binding hash and lockfile verification; 4. container build and digest capture when containers are used; 5. config validation and a real service/client smoke test in the relevant network namespaces. Fail CI on a dirty checkout, an unexpected path or patched dependency, a changed lockfile, artifact hash drift, a tag-only container reference where a digest is required, or a service/client smoke failure. Do not silently skip a gate. The job summary must list every check as `passed`, `failed`, or `skipped`, and every skipped check must include the exact reason, affected context, and consequence (for example, **not clean-room tested** or **network deployment unverified**). When reporting a failure, include the labeled execution context, exact command and working directory, exit status, first actionable error plus the unabridged log location, repository and dependency provenance, artifact/image hashes, and all skipped checks. Do not replace a failed service run with builder construction or a local-only success. ## Common pitfalls * `stoffel run --config` is network/off-chain config, not project `Stoffel.toml`. * Do not duplicate lower-level networking/protocol logic in app code. * Do not treat participant clients and the application control plane as one trust role. * Do not add plaintext private fields to control-plane endpoints or persistence. * Do not put `.with_client_input(...)` or `.execute_local()` in a production application-service path. * Do not silently replace missing browser/client support with a plaintext backend gateway. * Do not bypass typed IO validation for ClientStore apps. * Do not send participant values through an application server or SDK server/node builder; private input submission belongs to each SDK client. * Keep on-chain coordinator paths marked advanced until public docs and stable APIs exist. * Present coordinator/network assumptions with explicit current status and deployment validation guidance. * Do not move to network debugging until the local loop has produced a real passing or failing run. * Do not hide missing production process startup behind local SDK examples; record the lower-layer service command or mark it as an operator handoff. * Do not let an undeclared host mount, path dependency, `[patch]`, package cache, or loopback address make a container/CI run appear portable. # Stoffel App Troubleshooting Source: https://docs.stoffelmpc.com/developer-skills/stoffel-app-troubleshooting Diagnose app-level init, check, build, run, local MPC, binding, SDK, and network failures with evidence. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use current public crates.io releases by default, then the official GitHub repository at a full immutable revision when the needed change is not published. A local checkout is a separate, explicitly requested, nonportable framework-development workflow. ## Use when Use this playbook when a Stoffel app fails to init, check, build, run, test, execute locally, generate bindings, or connect through the SDK/network path. ## Goal Diagnose app-level issues systematically and collect real evidence before reporting success. ## First commands From the app root: ```sh theme={null} stoffel status --verbose stoffel check stoffel build --program-info ``` If the app has tests: ```sh theme={null} stoffel test --verbose ``` If Rust SDK code is involved: ```sh theme={null} cargo check --locked cargo test --locked ``` If local MPC is involved: ```sh theme={null} stoffel run --timeout-secs 180 ``` Label every command with its execution context: **app checkout**, **framework checkout**, **deployment host**, **container**, or **CI**. Include the working directory; do not merge output from different contexts into one unlabeled transcript. ## Portability and provenance diagnosis Capture provenance before changing code or dependencies: * app repository URL, commit, branch/tag if relevant, and clean/dirty state; * framework repository URL, commit, and clean/dirty state if a framework checkout is in use; * CLI installation source, resolved executable path, and reported version/build identity when available; * SDK source (public package version or git URL and revision, or the exact intentional local path); * committed lockfile path and hash, or why no lockfile applies; * bytecode hash and generated binding/manifest hashes; * immutable build/runtime image digest when containers are involved. For Rust consumers, inspect the resolved graph rather than trusting the manifest alone: ```sh theme={null} cargo metadata --locked --format-version 1 cargo tree ``` Inspect all relevant `Cargo.toml` files for `[patch]`, `[replace]`, `path =`, git revisions, and workspace-inherited dependencies. Inspect repository and applicable user/global `.cargo/config` and `.cargo/config.toml` files for source replacement, registries, aliases, target settings, and injected build flags. Record the resolved SDK package source from `cargo metadata` and compare it with the lockfile. Do not delete a patch or path dependency until you know whether it was intentional; do not call the result portable while it remains dependent on undeclared local state. Reproduce the failure from a **clean external app checkout**, outside the framework repository, with only documented public CLI/SDK dependencies and the committed lockfile. The public dependency consumer proof must check/build, produce bytecode and bindings, and exercise the relevant client-facing path without framework workspace inheritance, source mounts, unpublished package caches, or dirty generated files. Framework workspace tests do not substitute for this proof. If that external proof was not run or requires local framework state, classify the result explicitly as **not clean-room tested**. State why, instead of reporting a portability success. ### Container and CI diagnosis * Record the image digest, network mode, published ports, working directory, environment overrides, and every host-to-container mount. * Inspect mounts for framework checkouts, host `target/` directories, local SDK paths, package caches containing unpublished builds, stale generated bindings, and shadowed config/lockfiles. * Hash bytecode and bindings both outside and inside the container. Treat a mismatch as bundle mutation or a mount-selection failure. * Remember that `localhost`/`127.0.0.1` names the current network namespace. Record bind and advertised addresses separately, then test coordinator, mesh, and RPC reachability from the failing peer/client context. * In CI, make clean checkout, public dependency resolution, lockfile stability, artifact hash agreement, image digest capture, config validation, and real service/client smoke separate gates. Fail on unexpected path/patched dependencies or bundle drift. * Report every gate as `passed`, `failed`, or `skipped`. Every skip needs the exact reason, execution context, and impact; use **not clean-room tested** or **deployment unverified** where appropriate. ## Config checklist Inspect `Stoffel.toml`: * `[package]` exists. * `name` and `version` are non-empty. * `package.name` uses only letters, numbers, `-`, and `_`. * `build.source` is relative, inside the project, and either a `.stfl` file or source directory. * `build.target_dir` is a relative directory, not under `src/`, and not a file path. * `optimization_level` is `0..3` if present. * `[mpc].parties`, `[mpc].threshold`, and `[mpc].instance_id` are unquoted positive whole numbers where present. * Byzantine validation holds: HoneyBadger needs at least `4 * threshold + 1` parties. * Backend syntax is valid: `honeybadger`, `avss`, `avss:bls12_381`, `avss:bn254`, `avss:curve25519`, `avss:ed25519`, `avss:secp256k1`, `avss:p-256`. * `curve`/`field` is only used where a backend/curve combination supports it. ## Input checklist * Named function inputs use repeated `--input NAME=VALUE`. * ClientStore inputs use repeated `--client-input SLOT=VALUE`. * Repeating the same ClientStore slot appends values in order for `ClientStore.take_share(slot, index)`. * Do not combine multiple assignments in one flag. * Named input names must match function parameters exactly. * Client slots must be numeric. * If using input files, extensions must be `.json`, `.csv`, or `.txt`. * Named JSON inputs are an object like `{"a": 40, "b": 2}`. * Client JSON inputs are an object keyed by numeric slot like `{"0": [40, 2]}`. * Client CSV inputs require `slot,value` or `client_slot,value` headers. * TXT input files use one `name=value` or `slot=value` assignment per line; blank lines and `#` comments are ignored. * CLI values are integers, booleans, strings/JSON where accepted by file parsing, or `0x`-prefixed bytes where supported. ## Bytecode checklist * Rebuild `.stflb` after source changes. * Regenerate typed bindings after bytecode changes. * Explicit SDK backend/curve must match bytecode metadata. Prefer generated `ProgramManifest` for ClientStore apps. * Use `--program-info` or disassembly when diagnosing wrong entrypoint/function/client metadata. * If a source directory is configured, confirm the intended file was compiled; the CLI can compile multiple `.stfl` files. ## Local MPC checklist * Increase `--timeout-secs` before declaring protocol failure. * Avoid concurrent local party meshes that collide on ports/processes. * Verify whether the app uses named inputs or ClientStore inputs. * For examples with a first-line `# run-args:` header, copy those exact flags. * Include `--expected-output-clients N` for programs that call `MpcOutput.send_to_client` or `Share.send_to_client`. ## Typed bindings checklist * Bindings were generated from the same `.stflb` loaded at runtime. * Rust code includes generated bindings after generation. * `cargo check` was run after regeneration. * If duplicate crate/output collision errors appear with path or git dependencies, pre-generate bindings or remove the duplicate SDK build-dependency. ## Network/off-chain checklist * Local smoke passes before network work. * Network config is not project `Stoffel.toml`. * Client slot exists in program metadata. * Generated bindings came from the same `.stflb`. * Coordinator address, node RPC addresses, timestamp, identity material, and expected certificates are provided. * Network config validates server addresses, expected parties, expected clients, threshold, backend, and preprocessing. * Bytecode hash, generated binding version, party configs, coordinator settings, node RPC addresses, and identity material are from the same deployment bundle. * If live server startup is delegated to lower-layer tooling, capture the exact service command/supervisor logs instead of reporting SDK builder success as deployment success. ## Minimal diagnosis report When handing off a failure, include: * command run * working directory * `stoffel --help`/version if relevant * app `Stoffel.toml` with secrets removed * exact error output * whether `stoffel status --verbose` passed * whether `stoffel check` passed * whether bytecode was rebuilt * whether typed bindings were regenerated * exact `# run-args:` header if using an example * network/off-chain config shape with secrets redacted if relevant * bytecode hash and generated binding timestamp/hash for deployment failures * labeled execution context and exact working directory * app/framework repository URL, commit, and clean/dirty state * CLI source/path, SDK source, lockfile path/hash, and applicable image digest * relevant `cargo metadata` package source plus any `[patch]`, `.cargo` source replacement, local path, or workspace inheritance found * container mount map and bind/advertised addresses when applicable * exit status, first actionable error, and location of complete unabridged output * each check that passed, failed, or was skipped; include the reason and impact of every skip * clean external consumer proof result, or the explicit classification **not clean-room tested** ## Common pitfalls * Do not “fix” app issues by editing framework internals unless the task is explicitly a framework bug report. * Do not report success from a command that was not run. * Do not leak private client inputs, tokens, identity material, or secrets into notes, logs, Obsidian, HackMD, or public issues. * Do not store API tokens in `Stoffel.toml`, app repos, Obsidian, or HackMD. * Do not call deployment done from `execute_local()` output or SDK builder construction alone. * Do not mistake a host framework mount, package cache, `[patch]`, `.cargo` source replacement, or path dependency for public dependency portability. * Do not summarize a partial run as success: preserve the exact failure and list checks that never ran. # Stoffel CLI App Workflow Source: https://docs.stoffelmpc.com/developer-skills/stoffel-cli-app-workflow Use the stoffel CLI to init, check, build, compile, run, test, inspect, and troubleshoot Stoffel apps. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: generated applications use current public crates.io releases by default. A pinned official GitHub revision is the fallback; a local path is explicit, nonportable framework-development mode only. ## Use when Use this playbook when building, running, testing, inspecting, or troubleshooting a Stoffel application through the `stoffel` command. ## Current source of truth * `crates/stoffel-cli/src/main.rs` * `crates/stoffel-cli/src/project.rs` * `crates/stoffel-cli/tests/cli.rs` * `crates/stoffel-rust-sdk/src/input_file.rs` ## Core commands ```sh theme={null} stoffel init my-app # alias: stoffel new my-app stoffel status --verbose # alias: stoffel doctor --verbose stoffel check # validate source and project MPC settings stoffel build # build bytecode under target/debug/ stoffel build --release # build bytecode under target/release/ and default to O3 stoffel compile src/main.stfl --output target/debug/app.stflb stoffel compile --disassemble target/debug/app.stflb stoffel run # local MPC testing by default unless --network/--config is set stoffel dev --once # one build+run pass; omit --once for watch mode stoffel test --verbose # run no-argument Stoffel test functions stoffel clean --dry-run stoffel update --check # alias: stoffel upgrade --check ``` `stoffel run`, `build`, and `compile` accept a project directory, source directory, single `.stfl` file, or existing `.stflb` depending on the command. `build.source` may point at either a file or a source directory; when it is a directory, the CLI recursively compiles all `src/**/*.stfl` files. ## Project config shape A typical app has `Stoffel.toml`: ```toml theme={null} [package] name = "my-app" version = "1.0.0" # your app package version authors = [] [mpc] backend = "honeybadger" parties = 5 threshold = 1 # instance_id = 0 # curve = "bls12_381" # optional alias: field; mainly for AVSS backend [build] source = "src/main.stfl" # or "src" for a source directory target_dir = "target" # alias: output_dir # optimization_level = 2 ``` Rules enforced by the CLI: * `[package].name` and `[package].version` must be non-empty. * `package.name` may use only letters, numbers, `-`, and `_`. * `build.source` must be a relative `.stfl` file path or a relative source directory inside the app. * `build.target_dir` must be a relative directory inside the app and cannot be under `src/`. * `optimization_level` must be `0..3`. * `parties`, `threshold`, and `instance_id` must be unquoted positive whole numbers. * HoneyBadger requires Byzantine topology: at least `4 * threshold + 1` parties, with the current default of `5` parties and threshold `1`. ## Backend and curve flags Project config and CLI overrides support: ```sh theme={null} stoffel check --backend honeybadger --parties 5 --threshold 1 stoffel build --backend avss:bls12_381 stoffel build --backend avss:bn254 stoffel build --backend avss:curve25519 stoffel build --backend avss:ed25519 stoffel build --backend avss:secp256k1 stoffel build --backend avss:p-256 ``` `--protocol` aliases `--backend`; `--curve` aliases `--field` in CLI parsing. HoneyBadger does not take a curve suffix. ## App templates Use `stoffel init --help` for current template names. Current app-facing templates include: ```sh theme={null} stoffel init my-app # default Stoffel app + Rust wrapper files stoffel init my-lib --lib # library-style Stoffel source stoffel init my-rust-app --template rust # Rust app wrapper with nested stoffel/ project stoffel init my-python-app --template python stoffel init my-foundry-app --template solidity-foundry stoffel init my-hardhat-app --template solidity-hardhat ``` Treat non-Rust wrapper templates as integration scaffolds; use the Rust SDK for executable application flows. ## Audit generated Rust apps Immediately inspect every generated `Cargo.toml`; do not assume the CLI binary that generated it came from the same release as these docs. The portable default follows the versions on the current Rust SDK installation page: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", version = "" } [build-dependencies] stoffel-bindgen = "" ``` If a required fix is not published, pin both Stoffel crates to the official repository and a full 40-character revision. Keep them on the same revision: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", git = "https://github.com/Stoffel-Labs/stoffel.git", rev = "" } [build-dependencies] stoffel-bindgen = { git = "https://github.com/Stoffel-Labs/stoffel.git", rev = "" } ``` An adjacent path such as `path = "../stoffel/crates/stoffel-rust-sdk"` is allowed only when a framework contributor explicitly selects nonportable local-checkout mode. It must not appear in a generated app intended for another user or repository. Generated binary applications must include and commit `Cargo.lock`. After auditing `Cargo.toml`, regenerate the lockfile and use it for every check: ```sh theme={null} APP_MANIFEST="/absolute/path/to/generated-app/Cargo.toml" cargo generate-lockfile --manifest-path "$APP_MANIFEST" cargo check --locked --manifest-path "$APP_MANIFEST" cargo test --locked --manifest-path "$APP_MANIFEST" cargo metadata --locked --format-version 1 --manifest-path "$APP_MANIFEST" \ > "${APP_MANIFEST%/*}/cargo-metadata.json" ``` Audit `cargo-metadata.json`, not only manifest text. Each Stoffel package's `source` must be `registry+...` or the pinned official `git+https://github.com/Stoffel-Labs/stoffel.git?...#`. A `null` source identifies a path/workspace package and fails the portable-app check. Prove portability from a clean external checkout with no sibling Stoffel repository: ```sh theme={null} PROOF_DIR="$(mktemp -d)" APP_COMMIT="" git clone "" "$PROOF_DIR/app" git -C "$PROOF_DIR/app" checkout --detach "$APP_COMMIT" cargo check --locked --manifest-path "$PROOF_DIR/app/Cargo.toml" ``` Repository scripts must derive their root from the script file; callers may invoke them from any directory: ```sh theme={null} SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" APP_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" STOFFEL_ROOT="$APP_ROOT" if [ ! -f "$STOFFEL_ROOT/Stoffel.toml" ] && [ -f "$APP_ROOT/stoffel/Stoffel.toml" ]; then STOFFEL_ROOT="$APP_ROOT/stoffel" fi test -f "$STOFFEL_ROOT/Stoffel.toml" stoffel check "$STOFFEL_ROOT" cargo check --locked --manifest-path "$APP_ROOT/Cargo.toml" ``` ## Inputs Named function inputs use repeated `--input` flags: ```sh theme={null} stoffel run src/main.stfl --input a=40 --input b=2 ``` ClientStore inputs use repeated `--client-input` flags. Repeating the same slot appends values in order for that client: ```sh theme={null} stoffel run examples/mpc_top_k/main.stfl \ --client-input 0=50 --client-input 0=20 --client-input 0=40 \ --client-input 0=10 --client-input 0=30 \ --expected-output-clients 1 ``` Do not pass comma-separated assignments like `--input a=1,b=2`. ## Input files Both named inputs and ClientStore inputs can be loaded from `.json`, `.csv`, or `.txt`. Named inputs: ```sh theme={null} stoffel run --input-file inputs.json stoffel run --input-file inputs.csv stoffel run --input-file inputs.txt ``` Formats: ```json theme={null} {"a": 40, "b": 2} ``` ```csv theme={null} a,b 40,2 ``` ```txt theme={null} # one name=value per line a=40 b=2 ``` ClientStore inputs: ```sh theme={null} stoffel run --client-input-file client-inputs.json --expected-output-clients 1 stoffel run --client-input-file client-inputs.csv --expected-output-clients 1 stoffel run --client-input-file client-inputs.txt --expected-output-clients 1 ``` Formats: ```json theme={null} {"0": [40, 2], "1": [7]} ``` ```csv theme={null} slot,value 0,40 0,2 1,7 ``` ```txt theme={null} # repeated slots append in order 0=40 0=2 1=7 ``` Values may be integers, unsigned integers where supported, booleans, strings, JSON arrays/objects, or `0x`-prefixed bytes depending on the execution path. ## Bytecode and inspection ```sh theme={null} stoffel build --program-info # build stats are printed after bytecode write stoffel run target/debug/app.stflb --program-info stoffel compile --disassemble target/debug/app.stflb ``` `--program-info` on `run` prints function/instruction metadata and client IO metadata before execution. ## Validation / done criteria For a CLI workflow change or app setup, collect real output from: ```sh theme={null} stoffel status --verbose stoffel check stoffel build stoffel run --timeout-secs 180 ``` If tests exist: ```sh theme={null} stoffel test --verbose ``` For secret examples copied from the repository, use the exact first-line `# run-args:` header when present. ## Common pitfalls * `stoffel run --config` expects network/off-chain config, not project `Stoffel.toml`. * `stoffel init` creates a project directory, not a single file. * Do not accept generated `path = "../stoffel/..."` dependencies as a portable default. * Do not use `branch = "main"`, a tag, or an abbreviated Git SHA as the fallback; pin a full official revision. * Do not validate only from inside the Stoffel framework checkout, where workspace state can conceal dependency leaks. * Do not rely on `cd app && ...` in automation; pass explicit paths derived from the script or manifest. * If a path already contains `Stoffel.toml`, use `stoffel status` or `stoffel run`; do not re-init unless intentionally refreshing template files with `--force`. * Do not pass named inputs to ClientStore programs or ClientStore inputs to normal function-argument programs. * Do not claim a command works unless it was actually run. ## Next playbooks * [Stoffel-Lang App Programming](/developer-skills/stoffel-lang-app-programming) * [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) * [Stoffel App Troubleshooting](/developer-skills/stoffel-app-troubleshooting) # Stoffel Deployment Runbook Source: https://docs.stoffelmpc.com/developer-skills/stoffel-deployment-runbook Prepare and verify a production-shaped Stoffel deployment with compiled bytecode, typed client IO, node topology, coordinator settings, identity material, and operational checks. > Scope: app/operator playbook for moving a verified Stoffel app from local MPC into a production-shaped network deployment. This is not a maintainer guide for protocol internals. > > Dependency assumption: use public CLI/SDK surfaces first. If a current SDK wrapper does not start the lower-layer service directly, state the lower-layer handoff instead of pretending local MPC is deployment. ## Use when Use this playbook when a Stoffel app already passes local CLI/SDK smoke tests and now needs deployable artifacts, node/client topology, off-chain coordinator config, or an operator runbook. ## Goal Make deployment explicit enough that an agent does not guess hidden topology, identity, bytecode, or client-slot details. A deployment is complete only when a client can submit typed inputs to the intended network and receive the authorized output, or when the remaining lower-layer blocker is recorded with logs. ## Deployment model Production-shaped multi-user Stoffel apps have four distinct roles: 1. Build artifacts: `.stflb` bytecode, generated typed bindings, program manifest, and app release metadata. 2. Application control plane: public metadata, authorization, session lifecycle, client-slot/capability assignment, network discovery, non-sensitive receipts, and explicitly authorized opened aggregates. 3. Participant MPC clients: participant-owned processes that load deployment config and bindings, validate their owner's plaintext locally, select an assigned client slot, submit directly to the MPC service, and read authorized outputs. 4. MPC service plane: a separately deployed coordinator plus long-running MPC parties/nodes with stable addresses and identity material. For client-owned private input, the application control plane must not receive or persist plaintext. If a backend gateway accepts raw input before secret sharing, name the weaker trust model and require explicit approval. Do not introduce a gateway merely because direct browser/client support is unavailable. Do not use `.execute_local().await?` as the deployment model. It is a trusted local development analogue that spawns several MPC nodes/processes on one machine and may expose every fixture input to one harness process. ## Portability and provenance preflight Label each command and artifact observation with one execution context: **app checkout**, **framework checkout**, **deployment host**, **container**, or **CI**. A host command does not prove what a container sees, and a framework workspace command does not prove that an external app can consume the public packages. Before building the release bundle, record: * app repository URL, commit, and clean/dirty state; * framework repository URL, commit, and clean/dirty state when a framework checkout is intentionally involved; * CLI installation source, resolved executable path, and reported version/build identity when available; * SDK source as a public package version or git URL plus revision; record any intentional path source explicitly; * committed lockfile path and hash, or the reason a lockfile does not apply; * bytecode, generated binding, and program/deployment manifest hashes; * immutable container image digest for every applicable build and runtime image. The release candidate must pass in a **clean external app checkout** outside the framework repository. Resolve only documented public dependencies: no undeclared path dependencies, workspace inheritance, unpublished local packages, framework source mounts, or dirty generated files. Run a minimal public dependency consumer through check, build, binding generation, and its client-facing smoke path. Record its repository URL/commit, clean state, lockfile, dependency resolution, commands, and outputs. A framework-checkout test does not substitute for this proof. If the proof is unavailable, label the candidate **not clean-room tested** and do not present it as portable. ## Deployment inputs to collect Before editing deployment code, fill this table: | Field | Value | | -------------------------------------------------- | -------------------------------------------------------------------- | | Bytecode path | `dist/program.stflb` | | Program hash / build ID | | | Backend | `honeybadger` or `avss:` | | Parties | | | Threshold | | | Expected input clients | | | Expected output clients | | | Coordinator host/port | | | Party mesh addresses | | | Node RPC addresses | | | Client slots | | | Participant client runtime(s) | | | Direct client-protocol support status | | | Application control-plane endpoint | | | Control-plane persistence allowlist | | | Non-sensitive receipt schema | | | Components forbidden from plaintext | | | Party identity files | | | Client identity files | | | Timestamp / deployment epoch | | | Preprocessing sizes | | | Persistence/state volume | | | Health/log endpoints | | | App repo URL / commit / clean state | | | Framework repo URL / commit / clean state, if used | | | CLI source / resolved path | | | SDK source | | | Lockfile path / hash | | | Bytecode / binding / manifest hashes | | | Build and runtime image digests, if used | | | Execution contexts used | app checkout / framework checkout / deployment host / container / CI | | Clean external consumer proof | pass / fail / skipped with reason | If any field is unknown, stop and discover it from source, generated metadata, deployment config, or operator input. Do not invent it. ## Artifact build step Build artifacts before deploying services: ```sh theme={null} stoffel status --verbose stoffel check stoffel build --program-info ``` For Rust app wrappers: ```sh theme={null} cargo check --locked cargo test --locked ``` Expected artifacts: ```text theme={null} dist/program.stflb dist/stoffel_bindings.rs or generated bindings in OUT_DIR release manifest with backend, parties, threshold, client slots, and output slots ``` Record all bundle member hashes in the deployment notes. Make the release bundle immutable after this point. Production services and clients must load this unchanged bytecode, bindings, manifest, and config set; do not rebuild, regenerate, or dynamically compile `.stfl` independently on deployment hosts. Recheck hashes at distribution, service startup, and client execution. ## Local gate before network work Run the same program shape locally: ```sh theme={null} stoffel run --timeout-secs 180 ``` For Rust SDK local smoke: ```sh theme={null} cargo run --locked --bin ``` The local gate must cover: * every ClientStore input slot used by deployment; * expected output clients; * backend/topology selection when configurable; * typed output decoding when generated bindings are used. ## Network config plan Use SDK builders for topology validation when possible: ```rust theme={null} let deployment = NetworkDeployment::builder([ "node-0.internal:19200", "node-1.internal:19201", "node-2.internal:19202", "node-3.internal:19203", "node-4.internal:19204", ]) .expected_clients(2) .threshold(1) .honeybadger() .consensus_timeout(std::time::Duration::from_secs(60)) .preprocessing(1000, 500) .build()?; let paths = deployment.save_toml_files("deploy/network")?; ``` Each party config must agree on: * party id; * bind address; * peer addresses; * expected parties; * expected clients; * threshold; * backend/curve; * preprocessing sizes; * coordinator/off-chain settings when using ClientStore IO. ## Server/node handoff A node service needs: * the exact `.stflb` bytecode; * the party’s network config; * coordinator address if using off-chain ClientStore IO; * node RPC bind address; * party certificate/key material; * expected client certificates; * persistent state location when the backend/workflow stores shares or commitments; * process supervisor and logs. SDK server builders validate app-level config: ```rust theme={null} let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = Stoffel::load_file(app_root.join("dist/program.stflb"))? .parties(5) .threshold(1) .honeybadger() .build()?; let servers = runtime.servers_for_deployment(&deployment); let server0 = servers[0].clone().build()?; ``` Current SDK server builders capture program/config/health metadata; live process startup may delegate to lower-layer networking/VM integration. If the selected SDK version does not expose a single production server start command, write the lower-layer command or service wrapper explicitly and mark it as an operator handoff, not as completed deployment. ### Container and host safeguards * Pin every image by digest and record that digest with the node/client result. * Record each host-to-container mount. Mount the immutable release bundle read-only where possible, keep state and identity mounts separate, and verify bundle hashes inside every runtime container. * Never mount a framework checkout, developer `target/`, or unpublished package cache to make a release work. Such a run is **not clean-room tested**. * Distinguish bind addresses from advertised/reachable addresses. `127.0.0.1` and `localhost` refer to the current network namespace and normally cannot identify a service across host/container boundaries. * Publish only required ports, and test coordinator, mesh, RPC, and client reachability from the actual source container/host. Record DNS resolution and the address used, not only a host-side health result. ## Participant-client integration Participant-owned client software should load deployment config and submit its owner's typed input directly to the separately deployed MPC service. A remote application backend is not the participant client. In a desktop application, the local Tauri/Rust process may be the participant client when it uses the supported SDK path locally on the participant's device. ```rust theme={null} let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = Stoffel::load_file(app_root.join("dist/program.stflb"))? .parties(5) .threshold(1) .honeybadger() .build()?; let offchain = runtime .offchain_client_config(0)? .coordinator("coordinator.example.com", 31415) .timestamp(deployment_timestamp) .node_rpc_addresses([ "node-0.example.com:40000", "node-1.example.com:40001", "node-2.example.com:40002", "node-3.example.com:40003", "node-4.example.com:40004", ]) .identity_files("client-0.crt", "client-0.key") .build()?; let client = runtime .client_for_deployment(&deployment) .offchain_io(offchain) .client_id(0) .connect() .await?; let outputs = client .run_typed::(Client0Inputs { input_0: 42 }) .await?; ``` If the requested participant runtime cannot perform direct client-protocol submission, stop at that capability decision or use an explicitly participant-controlled sidecar. Do not silently route plaintext through the application backend. A backend gateway remains possible only as an explicitly approved degraded-trust architecture in which the gateway sees the user's raw input before secret sharing. ## Operational checklist Before calling deployment done: * Bytecode and bindings come from the same build. * All nodes run the same bytecode hash and compatible configs. * The coordinator address is reachable from every node and client. * Node mesh ports and node RPC ports are reachable according to the topology. * Party and client identity files are present, readable by the process, and not committed. * Client slots and output slots match the generated manifest. * Preprocessing capacity covers the expected workload shape. * Application-service schemas, persistence, logs, traces, caches, queues, analytics, crash reports, and receipts contain no participant plaintext. * A plaintext-canary test confirms the application control plane is outside the private-input path. * Participant clients submit directly to the MPC service using verified runtime support. * Logs redact identity material, tokens, certificates, shares, and private client values. * Restart behavior is documented for the control plane, participant clients, coordinator, and nodes. * Rollback means reverting bytecode, bindings, config, and app client code together. * Repository, CLI, SDK, lockfile, artifact, and image provenance is recorded for each labeled execution context. * The external clean-checkout/public dependency consumer proof passed. * Every deployment host/container verified the unchanged release bundle hashes before startup. * The real service processes started and an actual SDK client traversed the intended coordinator/node service path and decoded the authorized typed output. Builder construction, config serialization, `.execute_local()`, `execute_local`, or any other local-only executor is not deployment completion. If no live service path is available, report the deployment as blocked or incomplete with the exact handoff and logs. Do not rebuild the bundle merely to make the client smoke pass. ## Verification commands Use the commands that exist for the app. A typical ladder is: ```sh theme={null} stoffel check stoffel build --program-info cargo check --locked cargo test --locked cargo run --locked --bin build-program cargo run --locked --bin validate-network-config cargo run --locked --bin client-smoke -- --client-slot 0 ``` For external services, also record: ```sh theme={null} curl -f http:///health ``` and the process supervisor status/log command used by the deployment target. ## CI/release gates Use separately visible gates for provenance capture, clean app build, public dependency consumer proof, lockfile/dependency inspection, bytecode/binding/manifest hash agreement, image digest capture, deployment config validation, service startup/health, cross-namespace reachability, and the real SDK client/service smoke. CI must fail on a dirty checkout, unexpected local/path/patched dependency, lockfile drift, mutable-only image identity, bundle mutation, or failed service/client flow. Never omit a gate from the report. Mark each one `passed`, `failed`, or `skipped`. For a skip, record the exact reason, execution context, owner/handoff, and impact; for example, **not clean-room tested**, **container provenance unknown**, or **deployment unverified**. A skipped completion gate cannot produce a successful deployment status. ## Failure report shape When deployment fails, capture: * bytecode hash; * branch/commit; * party id or client slot; * command run; * redacted config path; * first failing log line; * whether local MPC passed; * whether typed bindings matched; * whether the failure is build, config validation, network reachability, identity, coordinator, protocol, or output decoding; * labeled execution context and working directory; * exact command, exit status, first actionable error, and unabridged log location; * app/framework repository URLs, commits, and dirty state; * CLI source, SDK source, lockfile hash, bundle member hashes, and applicable image digest; * mount map and bind/advertised addresses when containers are involved; * every skipped check and why it was skipped. ## Common pitfalls * Calling local MPC deployment because it returned the right answer on one machine. * Treating the application backend, participant client, CLI, and browser as one `client/app` role. * Accepting participant plaintext in control-plane APIs, persistence, queues, logs, or generic payload blobs. * Using `.with_client_input(...)` or `.execute_local()` in a production application-service path. * Replacing unsupported direct browser/client submission with an unapproved plaintext gateway. * Letting each node compile source independently instead of distributing pinned bytecode. * Running clients against bindings generated from stale bytecode. * Forgetting node RPC addresses when using off-chain ClientStore IO. * Committing client or party keys. * Treating backend/curve changes as config-only changes without rebuilding and revalidating artifacts. * Hiding a missing production server wrapper behind local SDK examples. ## Next playbooks * [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path) * [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) * [Stoffel Rust App SDK](/developer-skills/stoffel-rust-app-sdk) * [Stoffel App Troubleshooting](/developer-skills/stoffel-app-troubleshooting) # Stoffel Full App Golden Path Source: https://docs.stoffelmpc.com/developer-skills/stoffel-full-app-golden-path Build a complete Stoffel app from boundary design through StoffelLang, Rust SDK integration, local MPC validation, typed bindings, and deployment handoff. > Scope: AI-agent-agnostic playbook for building complete Stoffel applications. Use this as the orchestrating path, then switch to the narrower skills for implementation details. > > Dependency assumption: use portable public dependencies by default. A local checkout is a separate, nonportable framework-development workflow and is allowed only when explicitly requested. ## Use when Use this playbook when the task is larger than one `.stfl` snippet: an app idea needs a runnable Stoffel program, Rust host code, typed client IO, local MPC smoke tests, and a path toward network deployment. ## Goal Make an AI agent build the smallest complete app first, verify every layer, then expand. Do not let the agent stop at plausible code, skip local MPC, or treat `execute_local()` as deployment. ## Milestone 0: establish portability Complete this milestone before privacy design or implementation: 1. Discover and confirm the project root from the current working directory and repository markers such as `Stoffel.toml`, `Cargo.toml`, or `.git`. Do not invent or require a machine-specific path such as `/workspace/...`. 2. Select Stoffel and related project dependencies from public, reproducible sources in this order: the current crates.io release, then the official GitHub repository pinned to a full immutable commit SHA when the needed change is not published. 3. Reject floating branches and any default workflow that requires a local path, sibling checkout, or external filesystem checkout. 4. Use a local Stoffel checkout only if the user explicitly requests framework development. Label that route **nonportable**, document it separately, and keep the app's default build on public dependencies. 5. If no suitable public dependency is available, stop and report the missing dependency and attempted public sources instead of substituting a local checkout. Record the discovered root and selected public source in the task evidence. Keep this skill version-agnostic; concrete versions belong in installation docs and the app's dependency manifest. ## End-to-end sequence 0. Establish the dependency and filesystem portability contract. 1. Resolve the application trust architecture and participant runtime. 2. Define the privacy and output boundary. 3. Choose backend and topology. 4. Implement the minimal StoffelLang program. 5. Validate with the CLI. 6. Run local MPC with representative inputs. 7. Build participant-client SDK integration around the compiled program. 8. Generate typed client IO bindings from the exact bytecode. 9. Add app, privacy-boundary, and network smoke tests. 10. Prepare deployment artifacts and control-plane/client/network config. 11. Prove the app in a clean-room environment. 12. Hand off with real command output and remaining production assumptions. ## 1. Trust architecture and privacy boundary For client-owned private input, the input owner's device or process is the Stoffel MPC client. It submits the input through the Stoffel client protocol directly to the separately deployed MPC service. An application backend may provide public session configuration and receive non-sensitive lifecycle receipts or explicitly authorized opened aggregates, but it must not receive, deserialize, log, queue, cache, analyze, or persist participant plaintext. A backend gateway that accepts plaintext is a distinct, weaker trust model. Do not introduce it implicitly. Name it, document who can see the raw value, and require explicit approval before implementing it. Write this before coding: ```text theme={null} App outcome: - ... Secret values: - client 0: ... - client 1: ... Plaintext owner and location: - client 0 plaintext exists in: ... - client 1 plaintext exists in: ... Stoffel submission process: - participant runtime: native Rust / Tauri Rust / supported browser client / other - process that loads typed bindings and submits client slot inputs: ... Components forbidden from plaintext: - application service: ... - logs, traces, queues, caches, analytics, crash reports: ... Public values: - ... Application control-plane data: - room/session metadata: ... - client-slot/capability assignment: ... - non-sensitive submission receipt: ... Application-service persistence allowlist: - ... Authorized outputs: - host opens ... - client 0 receives ... - client 1 receives ... Non-goals / values not revealed: - ... Backend: - honeybadger / avss: - reason: Topology: - parties: - threshold: - input client slots: - output client slots: - application control plane: - participant MPC clients: - separately deployed coordinator and MPC parties: Runtime support: - direct client protocol supported in the participant runtime: yes / no / unverified - if no or unverified, stop or choose an explicitly participant-controlled sidecar; do not silently route plaintext through a backend ``` Rules: * Do not begin implementation while the plaintext location, submission process, forbidden components, persistence allowlist, or participant runtime support is unresolved. * Treat `client`, `app`, `backend`, `gateway`, and a local Tauri/Rust process as different roles. In a desktop app, the local Tauri/Rust process may be the participant client; a remote HTTP service is still an application backend. * Use HoneyBadgerMPC for ordinary private arithmetic over application values. * Use AVSS only when commitments, curve-compatible artifacts, or threshold-cryptography outputs are part of the app boundary. * Keep constants, thresholds, weights, encodings, and transcript bytes public unless they are genuinely private inputs. * Name concrete unauthorized visibility: who would otherwise see which raw input, trace, losing order, support signal, or log value. ## 2. Repository shape For a complete app, prefer this shape: ```text theme={null} stoffel-app/ Stoffel.toml mpc/ src/main.stfl src/.stfl dist/ # bytecode, manifest, and generated bindings clients/ participant/ # participant-owned Stoffel client and output handling services/ control-plane/ # public metadata, auth, session config, receipts, aggregates tests/ local-mpc/ # trusted local fixture injection no-private-ingress/ # service schemas/logs/storage reject participant plaintext deploy/ mpc-network/ # coordinator and party configuration control-plane/ # application-service configuration client-public-config/ # pinned program/network discovery fields ``` Keep reusable MPC logic in `.stfl` modules. Validate private input shape in the participant client before submission. Keep public metadata validation, authorization, session lifecycle, non-sensitive receipts, and public result mapping in the application control plane. Do not define participant private fields in control-plane request or persistence schemas. ## 3. Build the smallest StoffelLang program Start with one entrypoint and one representative output. Use typed secret annotations when the scalar shape is known: ```stfl theme={null} def main() -> None: var left: secret int64 = ClientStore.take_share(0, 0) var right: secret int64 = ClientStore.take_share(1, 0) var total: secret int64 = left + right MpcOutput.send_to_client(0, [total]) MpcOutput.send_to_client(1, [total]) ``` Then validate: ```sh theme={null} stoffel status --verbose stoffel check stoffel build --program-info stoffel run --client-input 0=40 --client-input 1=2 --expected-output-clients 2 --timeout-secs 180 ``` If the app uses named clear inputs instead of ClientStore, use `--input name=value`. Do not combine named inputs and ClientStore inputs in one local run unless that exact CLI/runtime version has been verified to support the combination. ## 4. Rust SDK boundaries Separate the participant client from the application control plane. The participant-owned Rust client should: * load pinned Stoffel bytecode, generated bindings, and public deployment config; * use its assigned client slot and identity material; * validate its owner's private input shape locally; * submit directly to the separately deployed coordinator/MPC parties; * decode only outputs authorized for that client. The application control plane may authenticate members, issue public session configuration and client-slot capabilities, record non-sensitive submission status, coordinate public lifecycle transitions, and persist explicitly authorized opened aggregates. It must not accept private input fields or call `.with_client_input(...)` with participant values. A trusted local smoke harness may load or compile the program, inject representative fixture inputs, spawn local MPC only for development testing, and decode outputs for program-semantic assertions. Local trusted-harness path: ```rust theme={null} let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let result = Stoffel::compile_file(app_root.join("src/main.stfl"))? .parties(5) .threshold(1) .expected_output_clients(2) .with_client_input(0, &[40_i64]) .with_client_input(1, &[2_i64]) .execute_local() .await?; ``` This local harness sees every fixture input. It proves program semantics, not that a production application service is outside the plaintext path. Deployment-shaped path: ```rust theme={null} let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = Stoffel::load_file(app_root.join("dist/program.stflb"))? .parties(5) .threshold(1) .honeybadger() .build()?; let offchain = runtime .offchain_client_config(0)? .coordinator("coordinator.example.com", 31415) .timestamp(deployment_timestamp) .node_rpc_addresses([ "node-0.example.com:40000", "node-1.example.com:40001", "node-2.example.com:40002", "node-3.example.com:40003", "node-4.example.com:40004", ]) .identity_files("client-0.crt", "client-0.key") .build()?; ``` Treat the deployment-shaped path as participant-client configuration. The participant-owned process selects its own slot, connects to the separately deployed MPC service, and submits its input without using the application control plane as a plaintext proxy. Treat `.execute_local().await?` as local testing where several MPC nodes/processes are spawned locally on the developer machine. ## 5. Typed client IO bindings After the first passing bytecode build: ```sh theme={null} stoffel build --program-info ``` Generate bindings from that exact `.stflb` using the project’s documented binding flow. In Rust, include the generated module and reference `ProgramManifest` when configuring typed clients. Rebuild bindings after any `.stfl` source or ClientStore shape change. ## 6. Verification ladder Run the narrowest check at each layer: ```sh theme={null} stoffel status --verbose stoffel check stoffel build --program-info stoffel run --timeout-secs 180 cargo check --locked cargo test --locked cargo run --locked ``` For docs or skill changes, also run: ```sh theme={null} npx mintlify validate npx mintlify broken-links git diff --check ``` Do not report success unless at least one command exercised each changed layer. For multi-user applications, also verify: * control-plane request and persistence schemas contain no participant private fields or generic private payload blobs; * a unique plaintext canary is absent from application-service requests, logs, traces, databases, caches, queues, analytics, crash reports, and receipts; * production application-service code does not call `.with_client_input(...)`, spawn local MPC nodes, or use `.execute_local()`; * participant clients can submit to the MPC network after public session bootstrap without proxying the input through the control plane; * each output is delivered only to its authorized recipient. ### Clean-room completion evidence Before declaring the app complete, test it from a fresh temporary directory or clean environment that has no Stoffel source checkout, sibling repository, or undeclared path dependency available. Starting from only the delivered app files, documented prerequisites, and network access to the selected public source: 1. install or resolve dependencies using the recorded public source; 2. run `cargo metadata --locked --format-version 1` and verify every external Stoffel package resolves from a registry or pinned official Git revision; 3. run the verification ladder for every delivered layer; and 4. capture the clean environment description, dependency-resolution output, commands, exit status, and relevant test or smoke output. A warm build in the implementation worktree is not clean-room evidence. If the clean build reaches for an external local path, a floating branch, an undeclared file, or an unavailable public dependency, the milestone fails; fix it or stop and report the blocker. ## 7. Deployment handoff Before handing the app to deployment work, produce: * `dist/program.stflb` built from the current source; * generated bindings from that bytecode; * parties, threshold, backend, curve, preprocessing sizes, and expected client slots; * coordinator address plan; * node mesh addresses and node RPC addresses; * client identity material requirements; * expected client/output slots; * exact local smoke command and output; * known production assumptions or SDK gaps. Then switch to [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook). ## Common pitfalls * Starting from Rust host code before the `.stfl` boundary is clear. * Treating a backend service, participant client, CLI, and Tauri Rust process as the same trust role. * Sending participant plaintext to an application endpoint in the default client-owned-input architecture; a gateway that does this is a separately approved degraded-trust design. * Using local fixture injection as evidence for the production private-input path. * Silently replacing unsupported browser/client functionality with a plaintext backend gateway. * Revealing intermediate secrets to make examples easier. * Treating local MPC as production deployment. * Compiling `.stfl` at runtime in production clients instead of loading pinned bytecode. * Forgetting to regenerate typed bindings after changing ClientStore shape. * Reporting generated code without running `stoffel check`, `stoffel build`, and a local MPC smoke. * Building only in a worktree that hides a required local or sibling Stoffel checkout. * Claiming completion without clean-room dependency resolution and execution evidence. ## Next playbooks * [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) * [Stoffel Rust App SDK](/developer-skills/stoffel-rust-app-sdk) * [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings) * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) * [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) # Stoffel-Lang App Programming Source: https://docs.stoffelmpc.com/developer-skills/stoffel-lang-app-programming Write .stfl application logic using supported Stoffel-Lang syntax, types, builtins, and example patterns. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use current public crates.io releases by default, then the official GitHub repository at a full immutable revision when the needed change is not published. A local checkout is a separate, explicitly requested, nonportable framework-development workflow. ## Use when Use this playbook when writing `.stfl` application logic or checking which language features are currently supported by examples and tests. ## Current source of truth * `crates/stoffel-lang/README.md` * `crates/stoffel-lang/examples/README.md` * `crates/stoffel-lang/examples/COVERAGE.md` * `crates/stoffel-lang/examples/**/*.stfl` * `crates/stoffel-lang/src/builtin_registry.rs` ## Minimal clear app ```stfl theme={null} def main(a: int64, b: int64) -> int64: return a + b ``` Run it: ```sh theme={null} stoffel run src/main.stfl --input a=40 --input b=2 ``` ## Supported app-language surfaces Current examples cover: * Pythonic indentation, `#` comments, `def` functions, typed and inferred returns, explicit `return`, `discard`, and `pass`. * `main` entry points plus no-argument test functions run by `stoffel test`. * Imports and import aliases. * Type aliases and scalar names: signed/unsigned 8/16/32/64-bit integers, `bool`, `float`/`float64`, `fix32`/`fix64`, strings, bytes aliases, `None`. * `secret T` type annotations for share-typed private values, including `secret int64`, `secret bool`, `secret fix64`, and nested forms such as `list[secret int64]`. * Integer literal widths/suffixes and hex literals. * Lists, dictionaries, nested generics, indexing, negative indexing, assignment through index, and slicing where covered by examples. * Dynamic objects, runtime field access, object schemas, base object syntax, and secret-typed fields. * `if` / `elif` / `else`, `while`, and `for` over ranges, lists, and strings. * End-exclusive ranges such as `0..10`. * Unary `-`, `not`, arithmetic, comparison, boolean operators, and compound assignment (`+=`, `-=`, `*=`, `/=`, `%=`). * `mod` for floored modulo and `%` for truncating remainder in number-theory examples. * Bitwise keywords on integers: `and`, `or`, `xor`, `not`, `shl`, `shr`. * Fixed-point arithmetic and comparisons with tolerance checks. * Field-access method syntax and object builtin syntax. * Closures exposed by the language stdlib. * Secret-share arithmetic with normal operators (`+`, `-`, `*`, `/`) where the value shape supports them, `secret bool` gates, and client-provided private inputs via `ClientStore`. `secret` is valid inside type annotations for parameters, return types, local variables, list elements, and object fields. Do not write `secret var` or `secret def`; write `var x: secret int64 = ...` or `def f(x: secret int64) -> secret int64:`. ## Unsupported or not runtime-ready today Do not present these as supported app syntax until examples/tests prove otherwise: * `enum` * `break` * `continue` * `yield` * `try/catch` They may have AST or lexer traces but are not represented as supported runtime examples. ## Builtins to know Core app builtins: * `print` * `type` * `append` / `push` * `len` * `slice` * `contains` / `in` * `assert` * `LocalStorage.store`, `LocalStorage.load`, `LocalStorage.retrieve`, `LocalStorage.delete`, `LocalStorage.exists` * `LocalStorage.load_share` for share-bearing stored values in advanced MPC examples * closure helpers such as `create_closure`, `create_closure_with_upvalue`, `call_closure`, `call_closure_with_arg`, `get_upvalue`, `set_upvalue` Switch to [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming) when code uses `secret`, `Share.*`, `ClientStore.*`, `Mpc.*`, `MpcOutput.*`, `Crypto.*`, `Bytes.*`, `Rbc.*`, `Aba.*`, or `Avss.*`. ## Example map Start with small local examples: * `local_control_flow`: loops, ranges, branching, arithmetic. * `local_collections`: list literals, indexing, append/push/len aliases. * `local_nested_generics`: generic functions over nested list shapes. * `local_storage`: local VM storage. * `local_dynamic_workflow`: dynamic objects, runtime type inspection, callbacks. * `local_closure_counter`: captured upvalues and stateful callbacks. * `local_text_processing`: string iteration. * `local_uint64_inverse`: overflow-safe unsigned modular inverse arithmetic. * `language_policy_engine`: imports, numeric widths, boolean logic, compound assignment. * `language_mpc_schemas`: object schemas with secret-typed fields. Use the algorithm gallery when looking for realistic app patterns: * `bits/clear/*`: popcount, bit reversal, parity, rotations, Gray code, flags, xorshift, etc. * `matrix/clear/*`: multiply, transpose, determinant, Fibonacci powers, Gaussian elimination, convolution, Markov power iteration, graph paths. * `polynomials/clear/*`: Horner evaluation, polynomial multiplication/division, calculus, interpolation, Newton/Chebyshev/Taylor examples. * `number_theory/clear/*`: Euclid, extended Euclid, modular inverse/CRT, LCM, modular exponentiation. ## Validation / done criteria For an app source change: ```sh theme={null} stoffel check path/to/main.stfl stoffel build path/to/main.stfl stoffel run path/to/main.stfl --timeout-secs 180 ``` For framework examples in the current docs: ```sh theme={null} cd /path/to/stoffel/crates/stoffel-lang ./examples/validate_examples.sh ``` For a single example with documented private input flags: ```sh theme={null} stoffel run crates/stoffel-lang/examples/mpc_top_k/main.stfl \ --client-input 0=50 --client-input 0=20 --client-input 0=40 \ --client-input 0=10 --client-input 0=30 \ --expected-output-clients 1 \ --timeout-secs 180 ``` ## Common pitfalls * Do not invent syntax from Rust, Python, or JavaScript without checking examples. * Keep app examples small and runnable. * Do not use unsupported control-flow constructs (`break`, `continue`) in developer-facing examples. * For secret examples, copy the `# run-args:` header and preserve client-slot ordering. * If a program uses secret values or `ClientStore`, switch to [Stoffel Secret MPC Programming](/developer-skills/stoffel-secret-mpc-programming). # Stoffel Local MPC Dev Loop Source: https://docs.stoffelmpc.com/developer-skills/stoffel-local-mpc-dev-loop Run local MPC smoke tests, ClientStore input flows, hot reload, and SDK local coordinator-backed execution. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: app dependencies come from current public crates.io releases by default. Use a full pinned revision of the official GitHub repository only as fallback; reserve local paths for explicit nonportable framework development. ## Use when Use this playbook when an app needs local MPC smoke testing, ClientStore input runs, hot reload, or SDK local coordinator-backed execution. ## Goal Give developers a repeatable local loop for testing private/MPC apps before any real network deployment. Local MPC is a verification gate, not the production topology. ## Current source of truth * `crates/stoffel-cli/src/main.rs` * `crates/stoffel-rust-sdk/README.md` * `crates/stoffel-rust-sdk/src/runtime.rs` * `crates/stoffel-lang/examples/README.md` * `crates/stoffel-lang/examples/**/*.stfl` ## CLI local run ```sh theme={null} stoffel run --timeout-secs 180 stoffel run path/to/main.stfl --timeout-secs 180 stoffel run target/debug/app.stflb --program-info --timeout-secs 180 ``` Local mode is the default unless `--network` or `--config` is set. ## Hot reload ```sh theme={null} stoffel dev --once --timeout-secs 180 stoffel dev --poll-ms 500 --timeout-secs 180 ``` Use `--once` for CI/smoke checks and default watch mode during interactive development. ## Input paths Named function args: ```sh theme={null} stoffel run --input a=40 --input b=2 ``` ```rust theme={null} .with_inputs(&[("a", 40_i64), ("b", 2_i64)]) ``` ClientStore values: ```sh theme={null} stoffel run --client-input 0=40 --client-input 0=2 --expected-output-clients 1 ``` ```rust theme={null} .with_client_input(0, &[40_i64, 2_i64]) .expected_output_clients(1) ``` The CLI also supports `--input-file` and `--client-input-file` for `.json`, `.csv`, and `.txt` inputs. See [Stoffel CLI App Workflow](/developer-skills/stoffel-cli-app-workflow). ## Use example `run-args` headers Many secret examples include the exact local flags in the first source line: ```stfl theme={null} # run-args: --client-input 0=50 --client-input 0=20 --client-input 0=40 --client-input 0=10 --client-input 0=30 --expected-output-clients 1 ``` Run by appending those flags: ```sh theme={null} stoffel run crates/stoffel-lang/examples/mpc_top_k/main.stfl \ --client-input 0=50 --client-input 0=20 --client-input 0=40 \ --client-input 0=10 --client-input 0=30 \ --expected-output-clients 1 \ --timeout-secs 180 ``` For repeated client slots, order matters: `--client-input 0=50 --client-input 0=20` maps to `ClientStore.take_share(0, 0)` then `ClientStore.take_share(0, 1)`. ## SDK local run For a Rust app, use the version from the current SDK installation docs and commit `Cargo.lock`: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", version = "" } ``` If a needed change is not released, use `git = "https://github.com/Stoffel-Labs/stoffel.git"` with `rev = ""`. Do not substitute `branch = "main"`. A `path = "../stoffel/crates/stoffel-rust-sdk"` dependency is nonportable and is valid only when deliberately testing framework source. ```rust theme={null} let result = runtime .local_network() .entry("main") .timeout(std::time::Duration::from_secs(180)) .run() .await?; ``` Builder shortcut: ```rust theme={null} let source = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("src/main.stfl"); let result = Stoffel::compile_file(source)? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .expected_output_clients(1) .execute_local() .await?; ``` ## Recommended local loop 1. Resolve the app root from an explicit argument, manifest, or the script's own location; do not assume the caller's CWD. 2. Run `stoffel status --verbose "$APP_ROOT"`. 3. Run `stoffel check "$APP_ROOT"` to catch syntax/config/type errors. 4. Run `stoffel build "$APP_ROOT" --program-info` to inspect bytecode and client IO metadata. 5. Run `stoffel run "$APP_ROOT" --timeout-secs 180` with named inputs or documented `# run-args:` flags. 6. If using Rust, run `cargo check --locked` and `cargo run --locked` with an explicit `--manifest-path` against the same bytecode/source. 7. Inspect `cargo metadata --locked` and prove the app in a clean external checkout. 8. Record the exact command/output in the app handoff. 9. Only then move to network/off-chain config with [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook). For a repository script, derive a stable root from the script path: ```sh theme={null} SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" APP_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" APP_MANIFEST="$APP_ROOT/Cargo.toml" STOFFEL_ROOT="$APP_ROOT" if [ ! -f "$STOFFEL_ROOT/Stoffel.toml" ] && [ -f "$APP_ROOT/stoffel/Stoffel.toml" ]; then STOFFEL_ROOT="$APP_ROOT/stoffel" fi test -f "$STOFFEL_ROOT/Stoffel.toml" stoffel check "$STOFFEL_ROOT" cargo check --locked --manifest-path "$APP_MANIFEST" cargo metadata --locked --format-version 1 --manifest-path "$APP_MANIFEST" \ > "$APP_ROOT/cargo-metadata.json" ``` The Stoffel package in metadata must have a `registry+...` source or a pinned official `git+...#` source. `source: null` exposes a local path/workspace dependency. ## Validation / done criteria For app local-MPC work: ```sh theme={null} stoffel status --verbose "$STOFFEL_ROOT" stoffel check "$STOFFEL_ROOT" stoffel build "$STOFFEL_ROOT" --program-info stoffel run "$STOFFEL_ROOT" --timeout-secs 180 cargo check --locked --manifest-path "$APP_ROOT/Cargo.toml" ``` Commit `Cargo.lock`, then clone the app into a temporary directory outside the framework checkout (with no sibling `../stoffel`) and rerun the locked check and local smoke. A pass inside the framework repository alone is insufficient portability proof. For framework example validation: ```sh theme={null} FRAMEWORK_ROOT="/absolute/path/to/stoffel" "$FRAMEWORK_ROOT/crates/stoffel-lang/examples/validate_examples.sh" STOFFEL_PROGRAM_NAME=mpc_runtime_info.stflb \ "$FRAMEWORK_ROOT/crates/stoffel-lang/examples/validate_examples.sh" --host-mpc ``` ## Common pitfalls * Compile-only success is not a local MPC smoke test. * A local MPC pass backed by an adjacent path dependency is not a portable app proof. * Do not let `cargo run` update the graph implicitly; commit the lockfile and use `--locked`. * Do not encode framework checkout locations or assume commands start at the repository root. * Local MPC success is not production deployment; it only proves the program and app boundary work on the local test network. * Increase `--timeout-secs` before assuming protocol failure. * Avoid port/process collisions by serializing tests that spawn local party meshes. * Keep `ClientStore` inputs separate from named function inputs. * Do not omit `--expected-output-clients` for examples/programs that send client outputs. * AVSS support is backend/curve/input dependent; verify the current SDK boundary. # Stoffel Rust App SDK Source: https://docs.stoffelmpc.com/developer-skills/stoffel-rust-app-sdk Embed Stoffel in Rust apps using the SDK for compilation, bytecode loading, local execution, clients, and servers. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: application examples use public crates.io releases by default. A pinned official GitHub revision is the fallback when the required public release is unavailable. A local path is an explicit, nonportable framework-development mode only. ## Use when Use this playbook when a Rust application embeds Stoffel compilation, bytecode loading, local execution, client/server builders, typed client IO bindings, or network/off-chain integration. ## Current source of truth * `crates/stoffel-rust-sdk/README.md` * `crates/stoffel-rust-sdk/src/lib.rs` * `crates/stoffel-rust-sdk/src/prelude.rs` * `crates/stoffel-rust-sdk/src/runtime.rs` * `crates/stoffel-rust-sdk/src/config.rs` * `crates/stoffel-rust-sdk/src/types.rs` * `crates/stoffel-rust-sdk/examples/*` ## Dependencies Use the released SDK dependency from the current Rust SDK installation docs. Keep the placeholder below synchronized with that page rather than copying a release number into this skill: ```sh theme={null} cargo add stoffel-rust-sdk --rename stoffel cargo add tokio --features macros,rt-multi-thread ``` Equivalent manifest shape: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", version = "" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` If that release does not contain a required fix, pin the official repository to a full 40-character commit SHA (not a branch, tag, or abbreviated SHA): ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", git = "https://github.com/Stoffel-Labs/stoffel.git", rev = "" } ``` Only framework contributors intentionally testing an adjacent checkout should use a path dependency: ```toml theme={null} # NONPORTABLE framework-development mode; never emit this in a distributable app template. [dependencies] stoffel = { package = "stoffel-rust-sdk", path = "../stoffel/crates/stoffel-rust-sdk" } ``` Do not use `path = "../stoffel/..."` as an application default, and do not combine `path` with `version` or `git` to make a locally dependent manifest appear portable. Use `use stoffel::prelude::*;` for app code. ## Path discipline Rust file APIs resolve relative paths from the process current directory, which may differ under tests, services, IDEs, and CI. Root app-owned paths at the Cargo manifest instead: ```rust theme={null} fn app_path(relative: impl AsRef) -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(relative) } ``` Use `app_path("src/main.stfl")`, `app_path("artifacts/program.stflb")`, and similar values with `compile_file`, `load_file`, and bytecode save/load calls. Accept deployment paths as explicit configuration when artifacts live outside the app; never depend on `cd` having been run first. ## Clear local execution ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { 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(()) } ``` ## Production-shaped client integration For application integration, design toward deployed services and packaged artifacts: build bytecode once, deploy MPC nodes separately, and have each participant-owned client load deployment config plus typed bindings. For client-owned private input, that participant process submits directly to the MPC service; an application backend remains outside the plaintext path. Use local MPC as the development smoke path, not the production topology. **Client input ownership rule:** private `ClientStore` values originate in the SDK client. The client validates the generated input shape, encodes its own vector, and submits it through the client/coordinator protocol. Application servers and SDK server/node builders receive deployment configuration and value-blind protocol metadata; they must not receive participant values or proxy plaintext private inputs. Do not add per-client value payloads to `ServerBuilder` to model client slots or input ranges. ```rust theme={null} let runtime = Stoffel::load_file(app_path("dist/program.stflb"))? .parties(5) .threshold(1) .honeybadger() .build()?; let offchain = runtime .offchain_client_config(0)? .coordinator("coordinator.example.com", 31415) .timestamp(deployment_timestamp) .node_rpc_addresses([ "node-0.example.com:40000", "node-1.example.com:40001", "node-2.example.com:40002", "node-3.example.com:40003", "node-4.example.com:40004", ]) .identity_files("client-0.crt", "client-0.key") .build()?; ``` Generated typed bindings should be compiled into the participant client. Production clients should load pinned bytecode/metadata; they should not compile `.stfl` source dynamically for every request. A backend gateway that accepts participant plaintext is a separate, degraded-trust architecture and requires explicit approval. ## Local MPC execution Use local MPC to verify program semantics before deploying. `.execute_local().await?` spawns a local MPC test network on the developer machine, and one harness process may see every fixture input. It does not prove that a production application service is outside the plaintext path. ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let result = Stoffel::compile( "def main() -> int64:\n var share = ClientStore.take_share(0, 0)\n return share.open()", )? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; assert_eq!(result[0].as_i64(), Some(42)); Ok(()) } ``` If the program sends outputs to client slots, configure the expected output clients before executing: ```rust theme={null} let result = Stoffel::compile_file(app_path("src/main.stfl"))? .expected_output_clients(2) .with_client_input(0, &[40_i64]) .with_client_input(1, &[2_i64]) .execute_local() .await?; ``` ## Loading and saving bytecode ```rust theme={null} let bytecode = app_path("target/debug/app.stflb"); let runtime = Stoffel::compile_file(app_path("src/main.stfl"))?.build()?; runtime.save_bytecode(&bytecode)?; let summary = runtime.bytecode_summary()?; let loaded = Stoffel::load_file(&bytecode)?.build()?; println!("functions: {:?}", summary.program.function_names); ``` ## Builder options to know Program source: * `Stoffel::compile(source)` * `Stoffel::compile_file(path)` * `Stoffel::load(bytes)` * `Stoffel::load_file(path)` MPC config: * `.parties(n)` * `.threshold(t)` * `.instance_id(id)` * `.honeybadger()` * `.avss(Curve::Bls12_381)` / `.curve(curve)` * `.backend(MpcBackend::...)` * `.manifest::()` when using generated bindings Compiler options: * `.optimize(bool)` * `.optimization_level(0..=3)` * `.print_ir(bool)` * `.compiler_options(CompilationOptions { ... })` Inputs: * `.with_input("a", 40_i64)` * `.with_inputs(&[("a", 40_i64), ("b", 2_i64)])` * `.with_client_input(0, &[40_i64, 2_i64])` * `.with_client_inputs(&[(0, vec![...])])` * `.expected_output_clients(n)` Runtime: * `.build()` * `.summary()` / `runtime.summary()` * `.to_bytecode()` / `runtime.to_bytecode()` * `.save_bytecode(path)` / `runtime.save_bytecode(path)` * `.execute_clear()` * `.execute_local()` * `.execute_local_function("entry")` / timeout variants where appropriate * `runtime.client()`, `runtime.server(party_id)`, `runtime.offchain_client_config(slot)` ## SDK value model Use `stoffel::Value` at the SDK boundary: * `Value::I64`, `Value::U64`, `Value::Bool`, `Value::Float`, `Value::String`, `Value::Bytes`, `Value::List`, `Value::Object`, `Value::Unit`. * Convenience accessors: `as_i64`, `as_u64`, `as_bool`, `as_f64`, `as_str`, `as_bytes`, `as_list`, `as_object`, `is_unit`. Typed client IO maps current manifest types as: * integer shares -> `i64` * unsigned integer shares -> `i64`/integer Rust fields at generated boundary depending on manifest mapping * boolean secret integers -> `bool` * fixed-point shares -> `f64` See [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings) for generated structs and validation. ## Network config builders For deployment-oriented code, use builders instead of hand-rolled maps: ```rust theme={null} let config = NetworkConfig::builder() .party_id(0) .bind_address("127.0.0.1:19200") .expected_parties(5) .expected_clients(1) .peers([ (1, "127.0.0.1:19201"), (2, "127.0.0.1:19202"), (3, "127.0.0.1:19203"), (4, "127.0.0.1:19204"), ]) .threshold(1) .honeybadger() .consensus_timeout(std::time::Duration::from_secs(60)) .preprocessing(1000, 500) .build()?; config.validate_server_addresses()?; let server = StoffelServer::builder(0).network_config(&config).build()?; let client = StoffelClient::builder().network_config(&config).build()?; ``` For full deployment handoff, also capture coordinator address, node RPC addresses, identity material, bytecode hash, generated binding version, persistence/state location, and process supervision. See [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook). ## Validation / done criteria For a generated or handed-off Rust app, commit `Cargo.lock` and use the locked graph in verification and CI: ```sh theme={null} cargo generate-lockfile cargo check --locked cargo test --locked cargo run --locked ``` Audit both the manifest text and Cargo's resolved provenance. Run these from the app manifest explicitly, so the result does not depend on the caller's current directory: ```sh theme={null} APP_MANIFEST="/absolute/path/to/my-app/Cargo.toml" test -f "${APP_MANIFEST%/*}/Cargo.lock" grep -nE 'stoffel-rust-sdk|stoffel-bindgen|path[[:space:]]*=' "$APP_MANIFEST" cargo metadata --locked --format-version 1 --manifest-path "$APP_MANIFEST" > "${APP_MANIFEST%/*}/cargo-metadata.json" ``` Inspect the Stoffel packages in `cargo-metadata.json`: crates.io packages have a `registry+...` source; the fallback has a `git+https://github.com/Stoffel-Labs/stoffel.git?...#` source. A `null` source means a path/workspace package and fails the portable-app audit. The final portability proof must run from a clean checkout outside the Stoffel framework repository and without an adjacent `../stoffel` directory: ```sh theme={null} APP_REPO_URL="" APP_COMMIT="" PROOF_DIR="$(mktemp -d)" git clone "$APP_REPO_URL" "$PROOF_DIR/app" git -C "$PROOF_DIR/app" checkout --detach "$APP_COMMIT" cargo check --locked --manifest-path "$PROOF_DIR/app/Cargo.toml" cargo test --locked --manifest-path "$PROOF_DIR/app/Cargo.toml" ``` For repository scripts, derive paths from the script location instead of assuming the current directory: ```sh theme={null} SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)" cargo check --locked --manifest-path "$REPO_ROOT/apps/my-app/Cargo.toml" ``` For local MPC app paths: ```sh theme={null} cargo run --locked ``` Framework validation: ```sh theme={null} cargo test --locked -p stoffel-rust-sdk cargo run --locked -p stoffel-rust-sdk --example quickstart cargo run --locked -p stoffel-rust-sdk --example local_mpc_client_input ``` ## Common pitfalls * Do not use path dependencies as the default after crates.io publication. * Do not accept `git = "...", branch = "main"`; use the official URL and a full `rev`. * Do not omit a generated app's `Cargo.lock` or silently drop `--locked` in CI. * Do not treat a successful build inside the framework checkout as portability proof; workspace inheritance and nearby paths can hide leaks. * Do not simulate protocol behavior in app code; use SDK/runtime execution paths. * Do not route `ClientStore` values through an application server or add participant input payloads to SDK server/node builders. Input ownership and submission belong to the SDK client. * Do not present `.execute_local().await?` as a production deployment path. * Do not compile `.stfl` source dynamically inside production clients; load pinned bytecode and generated metadata. * Do not set an explicit backend that conflicts with bytecode metadata. Prefer generated manifests for ClientStore programs. * For `ClientStore` apps, validate client input shapes before network submission. * Do not use `stoffel-rust-sdk` as the binding generator build-dependency. Use the matching public `stoffel-bindgen` crate under `[build-dependencies]` as shown in the typed-bindings playbook. ## Next playbooks * [Stoffel Typed Client IO Bindings](/developer-skills/stoffel-typed-client-io-bindings) * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) * [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) * [Stoffel Deployment Runbook](/developer-skills/stoffel-deployment-runbook) # Stoffel Secret MPC Programming Source: https://docs.stoffelmpc.com/developer-skills/stoffel-secret-mpc-programming Build MPC apps with secret types, Share, ClientStore, Mpc, MpcOutput, and runnable private-input examples. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use current public crates.io releases by default, then the official GitHub repository at a full immutable revision when the needed change is not published. A local checkout is a separate, explicitly requested, nonportable framework-development workflow. ## Use when Use this playbook when an app handles private values, secret shares, client-provided inputs, MPC output delivery, protocol/runtime metadata, or secure algorithm examples. ## Goal Help developers write MPC-oriented Stoffel apps using `secret` types, `Share.*`, `ClientStore.*`, `Mpc.*`, `MpcOutput.*`, and related builtins, while preserving runnable local examples. For client-owned private input in a multi-user or networked application, the participant-owned process is the Stoffel MPC client and submits directly to the separately deployed MPC service. The application control plane must not receive or persist participant plaintext. Complete the trust-boundary worksheet in [Stoffel Full App Golden Path](/developer-skills/stoffel-full-app-golden-path), then use [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) for the production client path. ## Current source of truth * `crates/stoffel-lang/examples/README.md` * `crates/stoffel-lang/examples/COVERAGE.md` * `crates/stoffel-lang/examples/mpc_*` * `crates/stoffel-lang/examples/bits/secret/*` * `crates/stoffel-lang/examples/matrix/secret/*` * `crates/stoffel-lang/examples/polynomials/secret/*` * `crates/stoffel-lang/examples/number_theory/secret/*` * `crates/stoffel-lang/examples/avss_*` * `crates/stoffel-lang/examples/threshold_signatures/*` ## Minimal secret app ```stfl theme={null} def main(a: secret int64, b: secret int64) -> secret int64: return a + b ``` When run locally through the CLI/SDK, source/file programs returning a secret value may be wrapped/opened by the local execution path so app tests can assert clear outputs. For client-owned private inputs, use `ClientStore` in the Stoffel program. The CLI flags below inject plaintext into one trusted local harness process for program testing; they do not define the production application-service path: ```stfl theme={null} # run-args: --client-input 0=40 --client-input 1=2 --expected-output-clients 2 def main() -> None: var a: secret int64 = ClientStore.take_share(0, 0) var b: secret int64 = ClientStore.take_share(1, 0) var sum: secret int64 = a + b MpcOutput.send_to_client(0, [sum]) MpcOutput.send_to_client(1, [sum]) ``` The first argument to `ClientStore.take_share(client_slot, input_index)` is the client slot. The second is that client's ordered input index. Repeating `--client-input 0=...` appends inputs for slot `0` in order. ## Secret values and shares Common patterns: ```stfl theme={null} var x: secret int64 = Share.random() var y = Share.from_clear_int(5, 1) var sum: secret int64 = x + y var product: secret int64 = x * y var opened: int64 = sum.reveal() ``` Prefer `secret T` annotations when you know the share's scalar shape. The `secret` keyword belongs inside type annotations for parameters, return types, local variables, list elements, and object fields: ```stfl theme={null} def score(raw: secret int64, weight: int64) -> secret int64: var scaled: secret int64 = raw * weight return scaled + 10 ``` Do not use `secret` as a declaration modifier before `def` or `var`. Use `var x: secret int64 = ...`, not `secret var x = ...`. Use normal arithmetic operators (`+`, `-`, `*`, `/`) for secret values when the operation fits the value shape. Method/function forms such as `Share.add`, `Share.mul`, `.add_scalar`, and `.mul_scalar` remain useful when you want to call a specific builtin explicitly. For fixed-point client inputs: ```stfl theme={null} var fixed: secret fix64 = ClientStore.take_share_fixed(0, 0) ``` For boolean circuits: ```stfl theme={null} def gate_and(a: secret bool, b: secret bool) -> secret bool: return a * b def gate_not(a: secret bool) -> secret bool: return 1 - a def gate_or(a: secret bool, b: secret bool) -> secret bool: var ab: secret bool = gate_and(a, b) return a + b - ab def gate_xor(a: secret bool, b: secret bool) -> secret bool: var ab: secret bool = gate_and(a, b) return a + b - (ab * 2) ``` ## Client input shares Use `ClientStore` when participant-owned SDK clients provide private inputs through the coordinator/client path. Each client owns and submits its complete ordered input vector directly; do not place `.with_client_input(...)`, plaintext client fields, or private payload proxies in an application backend: ```stfl theme={null} var value = ClientStore.take_share(0, 0) var fixed = ClientStore.take_share_fixed(0, 1) var n_clients: int64 = ClientStore.get_number_clients() var n_input_clients: int64 = ClientStore.get_number_input_clients() var n_output_clients: int64 = ClientStore.get_number_output_clients() ``` CLI flags: ```sh theme={null} stoffel run src/main.stfl \ --client-input 0=42 --client-input 0=58 \ --client-input 1=7 \ --expected-output-clients 2 ``` Input-file equivalents are documented in [Stoffel CLI App Workflow](/developer-skills/stoffel-cli-app-workflow). Treat CLI flags and input files as trusted local fixtures. They prove program semantics, not that a production control plane is outside the plaintext path. ## Client outputs Send share outputs to clients when the runtime advertises that capability: ```stfl theme={null} if Mpc.has_capability("client-output"): MpcOutput.send_to_client(0, [result_share]) ``` Many current examples now document `--expected-output-clients N` in a first-line `# run-args:` header. Preserve that flag in local runs; without it, output-capable client slots may not be declared in the local runtime. ## Runtime metadata Useful app metadata: * `Mpc.party_id()` * `Mpc.n_parties()` * `Mpc.threshold()` * `Mpc.instance_id()` * `Mpc.protocol_name()` * `Mpc.curve()` / `Mpc.field()` * `Mpc.is_ready()` * `Mpc.has_capability(name)` * `Mpc.capabilities()` * `Mpc.rand()` / `Mpc.rand_int()` ## Example families to inspect MPC primitive examples: * `mpc_share_arithmetic` * `mpc_boolean_circuit` * `mpc_bitwise_share` * `mpc_random_bit` * `mpc_bit_decomposition` * `mpc_secure_comparison` * `mpc_select_minmax` * `mpc_aes128_circuit` * `mpc_client_private_score` * `mpc_client_federated_average` * `mpc_protocol_coordination` * `mpc_share_toolkit` Secure algorithm examples with recent client I/O headers: * Comparison and bit algorithms: `mpc_range_check`, `mpc_clamp`, `mpc_compare_family`, `mpc_is_zero`, `mpc_popcount_secret`, `mpc_msb_log2`, `mpc_lowest_set_bit`, `mpc_parity`, `mpc_bit_reverse_rotate`, `mpc_sign_extend`. * Oblivious data access/search: `mpc_oblivious_read`, `mpc_oblivious_write`, `mpc_mux_tree`, `mpc_linear_search`, `mpc_lookup_table`, `mpc_pattern_match`. * Arithmetic/number theory: `mpc_secure_division`, `mpc_modulo_secret`, `mpc_mod_constant`, `mpc_gcd`, `mpc_lcm`, `mpc_reciprocal`, `mpc_sqrt`, `mpc_horner_eval`, `mpc_secret_base_power`, `mpc_secret_exponentiation`, `mpc_modexp`, `mpc_modinv`, `mpc_transcendental`. * Sorting/ranking/arrays: `mpc_bitonic_sort`, `mpc_secure_shuffle`, `mpc_top_k`, `mpc_rank_order`. Gallery examples: * `bits/secret/*`: private bit/boolean circuits with `ClientStore` inputs. * `matrix/secret/*`: private matrix/vector and fixed-point examples. * `polynomials/secret/*`: polynomial, interpolation, coding, and private matching examples. * `number_theory/secret/*`: private GCD, modular inverse, CRT, MAC, equality, and Diophantine examples. Advanced protocol/crypto examples: * `avss_share_auditor` * `avss_certificate/*` * `threshold_signatures/*` ## Validation / done criteria For a secret app source change: ```sh theme={null} stoffel check path/to/main.stfl stoffel build path/to/main.stfl stoffel run path/to/main.stfl --timeout-secs 180 ``` For an example with a `# run-args:` header, use the exact flags from that header. Example: ```sh theme={null} stoffel run crates/stoffel-lang/examples/mpc_bitonic_sort/main.stfl \ --client-input 0=7 --client-input 0=3 --client-input 0=5 --client-input 0=1 \ --client-input 0=8 --client-input 0=2 --client-input 0=6 --client-input 0=4 \ --expected-output-clients 1 \ --timeout-secs 180 ``` Framework validation: ```sh theme={null} cd /path/to/stoffel/crates/stoffel-lang ./examples/validate_examples.sh ``` ## Common pitfalls * Do not use clear function arguments when the program expects `ClientStore` inputs. * Do not move client-owned `ClientStore` values into an application server or SDK server/node builder. Keep private input encoding and submission in each SDK client. * Do not reorder repeated `--client-input` values for the same slot; order is the per-client input index. * Do not omit `--expected-output-clients` for programs that call `MpcOutput.send_to_client` or `Share.send_to_client`. * Do not reveal intermediate private values in examples unless the algorithm intentionally opens that result. * Do not claim protocol behavior from static compilation alone; run local MPC or report the blocker. # Stoffel Typed Client IO Bindings Source: https://docs.stoffelmpc.com/developer-skills/stoffel-typed-client-io-bindings Generate and use Rust typed client input/output bindings from exact Stoffel bytecode manifests. > Scope: AI-agent-agnostic playbook for building applications with the Stoffel framework. This is not a maintainer guide for compiler, VM, protocol, or release engineering work. > > Dependency assumption: use public crates.io releases by default, a full pinned revision of the official repository as fallback, and local paths only in explicit nonportable framework-development mode. ## Use when Use this playbook when a Stoffel app uses `ClientStore` and a Rust client/server wants compile-time input/output structs generated from the exact app bytecode. ## Goal Generate Rust bindings from the exact `.stflb` program the app will execute, use those bindings for typed client IO, and let the manifest select/validate backend and client-slot IO shape. ## Current source of truth * `crates/stoffel-rust-sdk/src/codegen.rs` * `crates/stoffel-rust-sdk/src/types.rs` * `crates/stoffel-rust-sdk/src/program.rs` * `crates/stoffel-rust-sdk/src/client.rs` * `crates/stoffel-rust-sdk/tests/sdk_usage.rs` * `crates/stoffel-rust-sdk/tests/compile_fail.rs` ## Cargo dependency roles The runtime SDK belongs in `[dependencies]`; the dedicated generator belongs in `[build-dependencies]`. Use the matching versions from the current Rust SDK installation docs: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", version = "" } [build-dependencies] stoffel-bindgen = "" ``` If the required API is not published, pin both crates to the same full commit in the official repository: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", git = "https://github.com/Stoffel-Labs/stoffel.git", rev = "" } [build-dependencies] stoffel-bindgen = { git = "https://github.com/Stoffel-Labs/stoffel.git", rev = "" } ``` Do not put `stoffel-rust-sdk` in `[build-dependencies]` to generate bindings. Do not emit local `path = "../stoffel/..."` entries except in explicitly labeled, nonportable framework-development manifests. ## Mode A: exact-bytecode bindings Use this mode for deployment and whenever bindings must describe a specific `.stflb` artifact. Keep the artifact under the app repository (for example `artifacts/program.stflb`), and generate only into Cargo's `OUT_DIR`: ```rust theme={null} use std::path::PathBuf; fn main() -> Result<(), Box> { let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").ok_or("missing CARGO_MANIFEST_DIR")?); let bytecode = manifest_dir.join("artifacts/program.stflb"); let out_file = PathBuf::from(std::env::var_os("OUT_DIR").ok_or("missing OUT_DIR")?) .join("stoffel_bindings.rs"); println!("cargo:rerun-if-changed={}", bytecode.display()); stoffel_bindgen::generate_bindings(&bytecode, &out_file)?; Ok(()) } ``` Include the generated file: ```rust theme={null} include!(concat!(env!("OUT_DIR"), "/stoffel_bindings.rs")); ``` For non-standard crate paths or derives, call `stoffel_bindgen::generate_bindings_with_config` with the same rooted input/output paths and `stoffel_bindgen::BindingsConfig`. Runtime code must load the same artifact without assuming the process CWD: ```rust theme={null} let bytecode = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("artifacts/program.stflb"); let runtime = stoffel::Stoffel::load_file(bytecode)? .manifest::() .build()?; ``` ## Mode B: source-generated bindings Use source mode only when the application intentionally compiles source during its Cargo build. It is convenient for development but does **not** prove that bindings match a separately deployed bytecode file: ```rust theme={null} use std::path::PathBuf; fn main() -> Result<(), Box> { let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").ok_or("missing CARGO_MANIFEST_DIR")?); let source = manifest_dir.join("stoffel/src/program.stfl"); let out_file = PathBuf::from(std::env::var_os("OUT_DIR").ok_or("missing OUT_DIR")?) .join("stoffel_bindings.rs"); println!("cargo:rerun-if-changed={}", source.display()); stoffel_bindgen::generate_bindings_from_source( &source, &out_file, stoffel_bindgen::BindingsConfig::default(), )?; Ok(()) } ``` Emit one `cargo:rerun-if-changed=...` line for every imported source or other generator input. If the bytecode is built by a separate command, do not blur the modes: build it first, then use exact-bytecode mode against that output. ## Generated shapes The generator emits: * `ProgramManifest` * `impl stoffel::GeneratedProgramManifest for ProgramManifest` * `Client{slot}Inputs` for each client slot with declared inputs * `Client{slot}Outputs` for each client slot with declared outputs * ordered fields such as `input_0`, `input_1`, `output_0` * `TypedClientInputs` / `TypedClientOutputs` implementations Current type mapping: * integer shares -> `i64` * boolean secret integers -> `bool` * fixed-point shares -> `f64` Bindings can be generated for bytecode without ClientStore IO; the file still contains a `ProgramManifest` and a comment that no client IO was declared. ## Use manifest-backed config ```rust theme={null} let mpc = stoffel::MpcConfig::builder() .manifest::() .build()?; let app_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = stoffel::Stoffel::load_file(app_root.join("program.stflb"))? .manifest::() .build()?; ``` The manifest carries the bytecode backend plus per-client input/output types. Prefer it over hand-written backend/curve literals for ClientStore programs. ## Typed client call ```rust theme={null} let outputs: Client0Outputs = client .run_typed(Client0Inputs { input_0: 42_i64, }) .await?; ``` Advanced explicit manifest call: ```rust theme={null} let outputs = client .run_typed_with_manifest::(inputs) .await?; ``` ## Bytecode must be the contract Treat `.stflb` as the app/client contract: 1. Write or update `.stfl` source. 2. Build bytecode with the same backend/curve/topology assumptions that will be used at runtime. 3. Generate Rust bindings from that bytecode. 4. Compile the Rust client/server code. 5. At runtime, load the same bytecode and validate manifest/client IO shape before submitting inputs. If source changes, rebuild bytecode and regenerate bindings. Do not hand-edit generated structs. ## Multi-client and ordered-input guidance If a program has: ```stfl theme={null} var a = ClientStore.take_share(0, 0) var b = ClientStore.take_share(0, 1) var c = ClientStore.take_share(1, 0) ``` Expect generated shapes like: ```rust theme={null} Client0Inputs { input_0: ..., input_1: ... } Client1Inputs { input_0: ... } ``` In the CLI equivalent, repeat a client slot in the same order: ```sh theme={null} stoffel run program.stflb \ --client-input 0=40 --client-input 0=2 \ --client-input 1=7 \ --expected-output-clients 2 ``` ## Validation / done criteria * Regenerate bindings after bytecode changes. * Commit the generated app's `Cargo.lock`; run `cargo check --locked` to catch type mismatches. * Run the app's local smoke with the same bytecode. * For network/off-chain submissions, validate the runtime's program manifest against generated types before submitting. Audit dependency provenance without relying on the current directory: ```sh theme={null} APP_MANIFEST="/absolute/path/to/client/Cargo.toml" cargo metadata --locked --format-version 1 --manifest-path "$APP_MANIFEST" \ > "${APP_MANIFEST%/*}/cargo-metadata.json" ``` Both `stoffel-rust-sdk` and `stoffel-bindgen` must resolve from `registry+...` or the same pinned official `git+...#`. A `null` package source is a local path/workspace leak. Final proof is `cargo check --locked --manifest-path ...` from a clean checkout outside the Stoffel repository, with no adjacent framework checkout. Framework tests: ```sh theme={null} cargo test --locked -p stoffel-rust-sdk generate_bindings_emits_typed_client_io_from_stflb_manifest cargo test --locked -p stoffel-rust-sdk generated_bindings_type_check_federated_average_example cargo test --locked -p stoffel-rust-sdk --test compile_fail ``` ## Common pitfalls * Bindings must come from the exact `.stflb` deployed/executed. * Rebuild bytecode and regenerate bindings after any source, backend, or curve change. * Do not hand-edit generated binding files. * Do not bypass manifest validation when network clients submit real inputs. * Do not assume slot order from Rust struct field order alone; it follows ordered ClientStore metadata from bytecode. * Wrong: `stoffel = { path = "../stoffel/crates/stoffel-rust-sdk" }` in a distributable app. Right: crates.io, or the official Git URL plus full `rev`. * Wrong: `stoffel::generate_bindings(...)` from an SDK build-dependency. Right: `stoffel_bindgen` in `[build-dependencies]`. * Wrong: input/output paths relative to process CWD or generated files written into `src/`. Right: inputs under `CARGO_MANIFEST_DIR`, outputs under `OUT_DIR`, and explicit rerun directives. * Wrong: generating from source and claiming an independently built deployment artifact is identical. Use exact-bytecode mode for that claim. ## Next playbooks * [Stoffel App Network and Off-Chain Integration](/developer-skills/stoffel-app-network-and-offchain-integration) * [Stoffel Local MPC Dev Loop](/developer-skills/stoffel-local-mpc-dev-loop) * [Stoffel App Troubleshooting](/developer-skills/stoffel-app-troubleshooting) # Basic Usage Source: https://docs.stoffelmpc.com/getting-started/basic-usage Daily Stoffel CLI workflows for checking, building, running, testing, and debugging Stoffel projects. This page describes the `stoffel` CLI. The `stoffel` binary is a Cargo-like project tool for creating, checking, building, running, and iterating on Stoffel apps. It is the developer-facing entry point for local MPC workflows. ## Core workflow ```bash theme={null} stoffel init hello-mpc cd hello-mpc stoffel check stoffel build stoffel run ``` Use `stoffel --help` to see the installed command set. Current commands are: * `init` / `new`: create a project from a template. * `check`: validate source and MPC configuration without writing bytecode. * `compile`: compile a project, source directory, or `.stfl` file to `.stflb` bytecode, or disassemble existing bytecode. * `build`: build project bytecode under `target/`. * `run`: run a project, `.stfl` source file, or `.stflb` bytecode through local or network MPC execution. * `dev`: watch a project and rerun it on local MPC when files change. * `test`: run no-argument Stoffel test functions. * `status` / `doctor`: show project health and environment status. * `clean`: remove generated build artifacts. * `update` / `upgrade`: check or update the CLI and project dependency files. ## Creating projects ```bash theme={null} # Default Stoffel project stoffel init my-project # Supported templates stoffel init py-app --template python stoffel init rust-app --template rust stoffel init foundry-app --template solidity-foundry stoffel init hardhat-app --template solidity-hardhat # Template aliases stoffel init py-app --template py stoffel init foundry-app --template foundry stoffel init hardhat-app --template hardhat # Library-style Stoffel project stoffel init my-library --lib # Refresh template files in an existing directory without deleting unrelated files stoffel init . --force ``` `--lib` cannot be combined with `--template`. Use `stoffel init --help` for supported template names. ## Project structure The default template currently creates a Rust-backed Stoffel project: ```text theme={null} my-project/ ├── Cargo.toml ├── README.md ├── Stoffel.toml └── src/ ├── main.rs ├── main.stfl └── stoffel_bindings.rs ``` Other templates place the Stoffel program under a nested `stoffel/` directory and include the host-language scaffold for Python, Rust, Foundry, or Hardhat. ## `Stoffel.toml` `Stoffel.toml` is the project manifest. The CLI reads package, build, and MPC settings from this file. A minimal shape looks like: ```toml theme={null} [package] name = "my-project" version = "0.1.0" [build] source = "src/main.stfl" target_dir = "target" [mpc] backend = "honeybadger" curve = "bls12-381" parties = 5 threshold = 1 ``` Use command-line overrides while developing: ```bash theme={null} stoffel check --backend honeybadger --field bls12-381 --parties 5 --threshold 1 stoffel build --release --parties 5 --threshold 1 stoffel run --parties 5 --threshold 1 ``` Do not pass project `Stoffel.toml` to `stoffel run --config`. `--config` is for an MPC network/off-chain client TOML file; pass the project path as the positional `PATH` instead. ## Checking and compiling ```bash theme={null} # Validate the current project without writing bytecode stoffel check # Validate a source file or directory stoffel check src/main.stfl stoffel check src # Print compiler IR for debugging stoffel check --print-ir # Build bytecode under target/ stoffel build stoffel build --release # Compile a project, source directory, or single file stoffel compile stoffel compile src/main.stfl --output target/debug/main.stflb stoffel compile src/main.stfl -O3 --output target/release/main.stflb # Disassemble bytecode stoffel compile --disassemble target/debug/my-project.stflb ``` Stoffel bytecode uses the `.stflb` extension. Optimization flags accepted by `compile`, `build`, and source-compiling `run` include: ```bash theme={null} stoffel build -O0 stoffel build -O 2 stoffel build --opt-level 3 stoffel build --optimize # O2 unless --release selects O3 stoffel build --release # writes under target/release ``` ## Running programs ```bash theme={null} # Run the current project through local MPC stoffel run # Run a source file or bytecode file stoffel run src/main.stfl stoffel run target/debug/my-project.stflb # Select an entry function stoffel run --entry main stoffel run --function main # Pass named function inputs stoffel run --input a=40 --input b=2 # Load named inputs from json/csv/txt stoffel run --input-file inputs.json # Print function and instruction metadata before executing stoffel run --program-info ``` For programs that use `ClientStore.take_share`, provide client-provided private inputs by numeric slot: ```bash theme={null} stoffel run \ --client-input 0=42 \ --expected-output-clients 1 \ --parties 5 \ --threshold 1 ``` Local MPC is the default unless `--network` or `--config` is set. A local run spawns several MPC nodes/processes on your machine using the configured `parties` and `threshold`. ## Development watch mode `stoffel dev` runs a project through local MPC, then watches `Stoffel.toml` and source files for changes: ```bash theme={null} stoffel dev \ --parties 5 \ --threshold 1 ``` For scripts and CI, run once and exit: ```bash theme={null} stoffel dev --once ``` Tune watch latency with `--poll-ms`: ```bash theme={null} stoffel dev --poll-ms 250 ``` ## Testing `stoffel test` runs no-argument test functions from a project or test file. By default it searches recursively under `tests/`. ```bash theme={null} stoffel test stoffel test tests/math.stfl stoffel test --test addition stoffel test --verbose ``` Use local MPC execution for tests that need several local MPC nodes instead of the embedded no-network test runner: ```bash theme={null} stoffel test ``` ## Status, clean, and update ```bash theme={null} # Project health and environment diagnostics stoffel status stoffel doctor --verbose # Remove generated target artifacts stoffel clean stoffel clean --dry-run stoffel clean --all # Check update actions without mutating files stoffel update --check # Reinstall the CLI from a source checkout stoffel update --self-from-source ``` ## Debugging tips * Run `stoffel check --print-ir` before investigating runtime behavior. * Run `stoffel run --program-info` to inspect function and instruction metadata. * Use `stoffel compile --disassemble ` to inspect bytecode. * Use the Rust SDK wrapper when you want application code to compile and run the same Stoffel program. ## Next steps * [Quick Start](./quick-start) * [CLI Overview](../cli/overview) * [Rust SDK Examples](../rust-sdk/examples) # Your First MPC Project Source: https://docs.stoffelmpc.com/getting-started/first-project Build and run a small secret-shared eligibility example with the Stoffel CLI and Rust SDK workflow. This tutorial builds a small eligibility-score computation. The goal is to model one client value as a share, compute over that share, and reconstruct the derived score explicitly. This is a small local example. Use the same modeling steps with your own deployment and security validation when moving beyond the tutorial. Campaign-style Stoffel architecture diagram showing how a StoffelLang program becomes app-prepared inputs, a build contract, and networked MPC runtime execution. ## What you will use * The `stoffel` CLI. * The Rust SDK from crates.io when you run or adapt the Rust wrapper path. * A `Share`-based StoffelLang program. Finish [Installation](./installation) before starting. ## Create a project ```bash theme={null} stoffel init eligibility-demo cd eligibility-demo ``` Check the generated program first: ```bash theme={null} stoffel check stoffel build stoffel run --program-info ``` ## Replace `src/main.stfl` Use this simple `Share`-based program: ```stoffel theme={null} def normalize_score(raw_score: Share) -> Share: var adjusted = raw_score.add_scalar(25) return adjusted.mul_scalar(2) def main() -> int64: var private_score = ClientStore.take_share(0, 0) var eligibility = normalize_score(private_score) return eligibility.open() ``` The program keeps the input as a `Share` while it computes, then opens only the final eligibility score as the allowed tutorial output. ## Validate and build ```bash theme={null} stoffel check stoffel build ``` Inspect the bytecode if needed: ```bash theme={null} stoffel compile --disassemble target/debug/eligibility-demo.stflb ``` If your project name produces a different bytecode file name, list `target/debug` and use that path. ## Run locally with a client input Run the project through local MPC. This starts several MPC nodes/processes locally on your machine and feeds client slot `0` with the private input value: ```bash theme={null} cd /path/to/eligibility-demo stoffel run \ --client-input 0=42 \ --parties 5 \ --threshold 1 ``` The returned value is `(42 + 25) * 2`, not the raw client input. ## Iterate in watch mode ```bash theme={null} stoffel dev \ --client-input 0=42 \ --parties 5 \ --threshold 1 ``` For scripts, add `--once`. ## Run the same idea from Rust The Rust SDK is the primary application API for embedding Stoffel programs in Rust apps. Use the bytecode you already built from `src/main.stfl` instead of embedding a second sample program in Rust. In this example, `.execute_local().await?` performs local MPC testing by spawning several MPC nodes/processes on your machine: ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/eligibility-demo.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; println!("Result: {}", result[0]); Ok(()) } ``` ## Shape the local harness like a participant client This section is a trusted local development harness. The file handoff and `.with_client_input(...)` call below expose the fixture plaintext to the local harness process. They prove program behavior; they are not a production privacy architecture and must not be moved into an application backend. For client-owned private input in a deployed app, separate these roles: ```text theme={null} application control plane <-> participant-owned client -> separately deployed MPC network public metadata/config owns plaintext and submits coordinator and MPC parties receipts/authorized aggregate directly through client IO ``` The application control plane must not receive the private request field. The participant-owned client obtains public session and client-slot configuration, then connects to the MPC deployment directly. If the participant runtime cannot perform direct client submission, stop at that capability gap or use an explicitly participant-controlled sidecar; do not silently add a plaintext backend gateway. For this tutorial, keep everything on your development machine but split the participant client from the trusted local MPC harness: ```text theme={null} src/ ├── app.rs # ordinary app types and non-private decision logic ├── client.rs # client/request handling boundary ├── lib.rs # shared module exports ├── main.stfl # private MPC program └── bin/ ├── local_mpc.rs # initializes the Stoffel program and local MPC loop └── eligibility_client.rs # command-line client handler ``` Create `src/lib.rs`: ```rust theme={null} pub mod app; pub mod client; ``` Create `src/app.rs` for the ordinary application types: ```rust theme={null} #[derive(Debug, Clone)] pub struct EligibilityRequest { pub account_id: String, pub private_score: i64, pub public_region: String, } #[derive(Debug, Clone)] pub struct EligibilityDecision { pub account_id: String, pub public_region: String, pub eligibility_score: i64, pub approved: bool, } pub fn decision_from_score( request: EligibilityRequest, eligibility_score: i64, ) -> EligibilityDecision { EligibilityDecision { account_id: request.account_id, public_region: request.public_region, eligibility_score, approved: eligibility_score >= 100, } } ``` Create `src/bin/local_mpc.rs`. This is the trusted local development harness: it loads the bytecode built from `src/main.stfl`, waits for fixture requests, sees the local fixture plaintext, and runs each score through local MPC. It must not be used as an application backend. Stop it with `Ctrl-C` when you are done. ```rust theme={null} use std::{fs, path::Path, time::Duration}; use stoffel::prelude::*; use tokio::time::sleep; const WORK_DIR: &str = "target/dev-mpc"; const REQUEST_FILE: &str = "target/dev-mpc/request.txt"; const RESPONSE_FILE: &str = "target/dev-mpc/response.txt"; #[tokio::main] async fn main() -> stoffel::Result<()> { fs::create_dir_all(WORK_DIR)?; let runtime = Stoffel::load_file("target/debug/eligibility-demo.stflb")? .parties(5) .threshold(1) .build()?; println!("Local MPC process ready. Waiting for requests in {REQUEST_FILE}."); let mut last_request = String::new(); loop { if Path::new(REQUEST_FILE).exists() { let request = fs::read_to_string(REQUEST_FILE)?; if request != last_request { last_request = request.clone(); let private_score: i64 = request.trim().parse().map_err(|error| { Error::InvalidInput(format!("invalid private score: {error}")) })?; let result = runtime .clone() .with_client_input(0, &[private_score]) .execute_local() .await?; let eligibility_score = result[0] .as_i64() .ok_or_else(|| Error::InvalidInput("expected int64 eligibility score".into()))?; fs::write(RESPONSE_FILE, eligibility_score.to_string())?; println!("Handled private score for client slot 0."); } } sleep(Duration::from_millis(250)).await; } } ``` Create `src/client.rs`. This file represents participant-owned client code: it accepts its owner's request, sends the private score to the trusted local MPC harness, then combines the returned score with ordinary client-side fields. Do not expose `EligibilityRequest.private_score` as an application-service request schema. ```rust theme={null} use std::{fs, path::Path, thread, time::Duration}; use stoffel::prelude::*; use crate::app::{decision_from_score, EligibilityDecision, EligibilityRequest}; const WORK_DIR: &str = "target/dev-mpc"; const REQUEST_FILE: &str = "target/dev-mpc/request.txt"; const RESPONSE_FILE: &str = "target/dev-mpc/response.txt"; pub fn handle_eligibility_request( request: EligibilityRequest, ) -> stoffel::Result { fs::create_dir_all(WORK_DIR)?; if Path::new(RESPONSE_FILE).exists() { fs::remove_file(RESPONSE_FILE)?; } fs::write(REQUEST_FILE, request.private_score.to_string())?; let eligibility_score = loop { if Path::new(RESPONSE_FILE).exists() { let raw = fs::read_to_string(RESPONSE_FILE)?; break raw.trim().parse::().map_err(|error| { Error::InvalidInput(format!("invalid eligibility score: {error}")) })?; } thread::sleep(Duration::from_millis(250)); }; Ok(decision_from_score(request, eligibility_score)) } ``` Create `src/bin/eligibility_client.rs` as a command-line entry point for the client-handling code: ```rust theme={null} use eligibility_demo::app::EligibilityRequest; use eligibility_demo::client::handle_eligibility_request; fn main() -> stoffel::Result<()> { let mut args = std::env::args().skip(1); let account_id = args.next().unwrap_or_else(|| "acct_123".to_owned()); let private_score = args .next() .as_deref() .unwrap_or("42") .parse::() .map_err(|error| stoffel::Error::InvalidInput(format!("invalid score: {error}")))?; let public_region = args.next().unwrap_or_else(|| "ca".to_owned()); let decision = handle_eligibility_request(EligibilityRequest { account_id, private_score, public_region, })?; println!("{decision:?}"); Ok(()) } ``` Run the local MPC owner in one terminal: ```bash theme={null} cargo run --bin local_mpc ``` Then run the client handler from another terminal: ```bash theme={null} cargo run --bin eligibility_client -- acct_123 42 ca ``` The important local separation is that participant client handling lives in its own module and the Stoffel program owns the private computation. In this tutorial, `main.rs` starts local MPC nodes and sees fixture plaintext on the same development machine. In a deployed setup, the participant-owned client would load pinned bindings and public deployment config, then submit directly to the separately deployed network. A separate application control plane may manage public metadata, session lifecycle, non-sensitive receipts, and authorized opened aggregates, but it must remain outside the plaintext path. Want to deploy MPC apps instead of running local development networks? [Sign up for Stoffel updates](https://buttondown.com/stoffel) to hear when we launch the platform for deploying MPC apps. ## Design notes * Identify which input is secret-shared, which policy logic is public, and which computed result should be reconstructed. * Keep sensitive values as `Share` values while computing. * Use `open()` only for outputs you intentionally reveal. * For client-directed share outputs beyond this tutorial, prefer `MpcOutput.send_to_client` or `Share.send_to_client`. * Use `stoffel check --print-ir`, `stoffel run --program-info`, and bytecode disassembly when debugging. ## Next steps * [Basic Usage](./basic-usage) * [StoffelLang Overview](../stoffel-lang/overview) * [Rust SDK Examples](../rust-sdk/examples) # Installation Source: https://docs.stoffelmpc.com/getting-started/installation Install the Stoffel CLI and verify the `stoffel` command line tool for building Stoffel apps. The `stoffel` command line tool can currently be run on Linux and macOS, and on Windows through WSL2. Use this page to install the `stoffel` CLI, verify that the command is on your path, and create a first project. The CLI is the entry point for creating projects, checking source, building bytecode, and running local MPC development workflows. Using an AI coding agent? Start with the [agent install prompt](#install-stoffel-with-an-ai-coding-agent). It installs the `stoffel` CLI, adds Stoffel skills, connects docs access, and verifies the setup before the agent changes a Stoffel project. ## Requirements Required for the CLI: * A Unix-like shell: Linux, macOS, or Windows with WSL2 * `curl` or `wget` * 4 GB RAM minimum; 8 GB recommended Required only for source builds or repository examples: * [Rust and Cargo](https://www.rust-lang.org/tools/install) * [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) Required only for AI-agent setup: * [Node.js](https://nodejs.org/) for `npx`-based skill and MCP setup commands ## Prebuilt CLI (recommended) Install the `stoffel` CLI with the official installer: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh ``` The installer places `stoffel` in `~/.local/bin` by default. If your shell cannot find it, add that directory to your `PATH`: ```bash theme={null} export PATH="$HOME/.local/bin:$PATH" ``` Verify the installation: ```bash theme={null} stoffel --help ``` You should see commands for the normal project loop: ```text theme={null} init Create a new Stoffel project check Validate source and project MPC settings without writing bytecode build Build project bytecode under target/ run Run source or bytecode through local or network MPC execution dev Watch a project and rerun it through local MPC when files change ``` You can also check the installed version: ```bash theme={null} stoffel --version ``` ## Verify with a new project Create and build a project: ```bash theme={null} stoffel init hello-mpc cd hello-mpc stoffel check stoffel build ``` Expected output includes: ```text theme={null} Created Stoffel project at hello-mpc Checked .../hello-mpc/src/main.stfl Built .../hello-mpc/target/debug/hello-mpc.stflb ``` This verifies that the `stoffel` command line tool can create, check, and build a Stoffel app. The [Quick Start](./quick-start) continues from here into local MPC execution and the Rust SDK wrapper. ## Install Stoffel with an AI coding agent Paste this prompt into your AI coding agent if you want it to install Stoffel, connect the docs context, and prove the setup works: ```text theme={null} You are helping me build a Stoffel app. First, install Stoffel and the agent context for this workspace: 1. Confirm this terminal can run shell commands and that Node.js is available: node --version npm --version 2. Install the `stoffel` CLI binary and put it on PATH for this shell: curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" 3. Verify the CLI works: stoffel --version stoffel --help 4. Install the Stoffel developer skills: npx skills add https://docs.stoffelmpc.com --all 5. If this agent supports MCP, connect the live Stoffel docs: npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp 6. Verify docs access by listing the installed Stoffel skills and searching the docs for `ClientStore` or `local MPC` using the tools available in this agent harness. 7. Create and verify a smoke project: stoffel init hello-mpc cd hello-mpc stoffel status --verbose stoffel check stoffel build Use the main Stoffel docs as the source of truth for CLI, SDK, StoffelLang, and local MPC behavior. Use Developer Skills as task playbooks. After setup, use the Developer Skills overview to choose the right playbook for the task. Do not claim the setup is complete unless you can show real output from `stoffel --version`, `stoffel check`, and `stoffel build`. If a command fails, report the exact error and fix the root cause instead of inventing expected output. ``` ### Manual agent-context setup If you do not want your agent to install the CLI and skills, run the core setup yourself: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" stoffel --version stoffel --help npx skills add https://docs.stoffelmpc.com --all ``` List the available skills without installing: ```bash theme={null} npx skills add https://docs.stoffelmpc.com --list ``` Connect the live Stoffel docs through Mintlify's hosted MCP server when your agent supports MCP: ```bash theme={null} npx add-mcp --name stoffel-docs --transport http https://docs.stoffelmpc.com/mcp ``` After setup, continue to [Quick Start](./quick-start) or choose a playbook from [Developer Skills](/developer-skills/overview). ## Pin a CLI version Pin a version when you want every developer or CI job to use the same CLI: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh -s -- --version ``` For example, use `--version 0.1.0` when a project or CI job intentionally standardizes on that CLI version. Install into a different directory when your environment manages tool paths explicitly: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | STOFFEL_INSTALL_DIR="$HOME/bin" sh ``` ## Build the CLI from source Build from source when you need repository examples, workspace development, or local CLI changes. ```bash theme={null} git clone https://github.com/Stoffel-Labs/stoffel.git cd stoffel cargo build cargo build -p stoffel-cli --bin stoffel ``` The debug CLI path is: ```text theme={null} stoffel/target/debug/stoffel ``` For a faster CLI binary, build in release mode: ```bash theme={null} cargo build --release -p stoffel-cli --bin stoffel ``` The release CLI path is: ```text theme={null} stoffel/target/release/stoffel ``` Use the prebuilt CLI for the normal app development loop. Build the CLI from source only when you need local CLI changes or repository examples. ## Troubleshooting ### `stoffel` is not found Add the CLI install directory to your path: ```bash theme={null} export PATH="$HOME/.local/bin:$PATH" ``` If you installed into another directory, add that directory instead. ### `cargo` is not found Install Rust and load Cargo's shell environment: ```bash theme={null} curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" rustc --version cargo --version ``` ### Source builds take a while The CLI source build compiles several workspace crates. The first Cargo build may take several minutes while dependencies are fetched and compiled. ### Native Windows issues Use WSL2 unless you are specifically testing native Windows support. ## Next steps * [Quick Start](./quick-start) * [CLI Overview](../cli/overview) * [Rust SDK Overview](../rust-sdk/overview) # Quick Start Source: https://docs.stoffelmpc.com/getting-started/quick-start Create a Stoffel project, build bytecode, and run the generated program through local MPC. This guide gets you to a working Stoffel project. You will create the default Rust-backed project, check the StoffelLang source, build bytecode, run local MPC, and run the generated Rust wrapper. ## Copy-paste path Use this when you want the fastest default project check: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" stoffel --version stoffel init hello-mpc cd hello-mpc stoffel status --verbose stoffel check stoffel build stoffel run --timeout-secs 180 cargo build cargo run ``` Success means the CLI creates the project, `stoffel check` validates `src/main.stfl`, `stoffel build` writes `.stflb` bytecode under `target/`, the local MPC run completes, and the Rust wrapper builds and runs. ## Create a project After installing the `stoffel` CLI, create and enter a new project: ```bash theme={null} stoffel init hello-mpc cd hello-mpc ``` The default template creates: ```text theme={null} hello-mpc/ ├── Cargo.toml ├── README.md ├── Stoffel.toml └── src/ ├── main.rs ├── main.stfl └── stoffel_bindings.rs ``` Start with these files: * `src/main.stfl`: the StoffelLang program. * `Stoffel.toml`: package, build, and local MPC settings. * `src/main.rs`: a Rust wrapper that calls the same Stoffel program. * `src/stoffel_bindings.rs`: generated program metadata used by the Rust wrapper. ## Check and build Validate the Stoffel source and project configuration: ```bash theme={null} stoffel check ``` Expected output includes the checked source path and discovered functions: ```text theme={null} Checked .../hello-mpc/src/main.stfl (...) ``` Build bytecode under `target/`: ```bash theme={null} stoffel build ``` Expected output includes the generated `.stflb` file: ```text theme={null} Built .../hello-mpc/target/debug/hello-mpc.stflb Bytecode size: ... bytes Optimization: O2 (enabled) Profile: debug ``` ## Run local MPC Run the generated program through local MPC: ```bash theme={null} stoffel run ``` Local MPC testing runs the compiled program by spawning several MPC nodes/processes locally on your machine. The default project uses five parties with threshold one unless you override those settings in `Stoffel.toml` or with command-line flags. For one-shot development runs, use: ```bash theme={null} stoffel dev --once ``` `stoffel dev` checks the project, builds it, runs local MPC, and can watch the project when you omit `--once`. ## Run from Rust The generated `src/main.rs` calls the same `src/main.stfl` program from Rust application code. Build and run the wrapper: ```bash theme={null} cargo build cargo run ``` Use the Rust wrapper when you are ready to call Stoffel from an application rather than only from the CLI. ## Make one change Open `src/main.stfl`, make a small change, then rerun the fast loop: ```bash theme={null} stoffel check stoffel build stoffel run ``` If the generated program asks for inputs, check the generated `README.md` and `src/main.stfl`: * use `--input NAME=VALUE` for ordinary named function inputs; * use `--client-input SLOT=VALUE` for programs that call `ClientStore.take_share`. ## You now have * a Stoffel project created with `stoffel init`; * checked StoffelLang source; * compiled `.stflb` bytecode under `target/`; * a local MPC development run; * a Rust wrapper that calls the same program. ## Use with AI coding agents Delegating this quickstart to an AI coding agent? Complete the [agent setup in Installation](./installation#install-stoffel-with-an-ai-coding-agent) first so the agent has Stoffel skills and live docs access before it edits code. Require the agent to return real command output from: * `stoffel --version` * `stoffel check` * `stoffel build` * `stoffel run` or `stoffel dev --once` * `cargo build` or `cargo run` Do not accept a code-only summary for a runnable app task. If a command fails, the agent should report the exact error and fix the root cause instead of inventing expected output. ## Next steps Choose the next step based on what you want to do: * To understand the core CLI commands in more detail, read [Basic Usage](./basic-usage). * To build the smallest app-shaped Rust integration, run the [Rust SDK quickstart](../tutorials/rust-sdk-quickstart), then read [Rust SDK App Integration](../rust-sdk/app-integration) for the reusable pattern. * To build the first substantial tutorial app after the SDK quickstart, start [Private Matchmaking](../tutorials/private-matchmaking). # Introduction Source: https://docs.stoffelmpc.com/introduction Write StoffelLang programs, build `.stflb` bytecode, run local MPC, and integrate private computation into Rust applications. ## Build a Stoffel application Stoffel is a programming and runtime stack for the private computation inside an application. You write the private workflow in StoffelLang, compile it to `.stflb` bytecode, run it through local MPC during development, and call it from Rust application code with the SDK. Use these docs to: * install the `stoffel` CLI; * create a project; * check and build `.stflb` bytecode; * run local MPC on your machine; * call a Stoffel program from Rust; * understand how shares, parties, thresholds, and openings fit into the application boundary. ## The private-computation boundary Most application stacks encrypt data at rest and in transit, then decrypt it so the app can run its logic. That makes sensitive user context part of ordinary application state: available to services, logs, analytics, support tools, operators, and downstream integrations unless every layer is carefully constrained. Stoffel gives you a different boundary for the sensitive part of the workflow. Private values enter the computation as shares. The program computes over those shares. Only explicit openings or client outputs leave the boundary. Side-by-side Stoffel-branded data-flow comparison: the usual app stack decrypts user data into plaintext app state that fans out to logs, databases, analytics, and support systems; the Stoffel stack splits user data into shares, sends only shares across the private-computation boundary, runs MPC parties that hold shares only, and returns authorized output through an explicit opening or client output. Read the diagram as a data-form comparison. In the usual app stack, decrypting to compute turns sensitive context into plaintext app state that can spread through ordinary infrastructure. In the Stoffel stack, plaintext stays at the app/client edge; shares cross the private-computation boundary; MPC parties exchange protocol messages while holding shares only; and only an explicit authorized output leaves. ## How the stack fits together Docs-optimized Stoffel stack diagram showing app integration, language and CLI, bytecode and VM, and MPC runtime layers. | Layer | Developer-facing surface | What it does | | --------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------- | | App integration | Rust SDK / generated bindings | Connects product code to Stoffel programs, inputs, outputs, local checks, and local MPC runs. | | Language + CLI | `.stfl` source / `stoffel` command | Checks source, builds bytecode, runs project workflows, and drives local development loops. | | Bytecode + VM | `.stflb` / register VM | Loads the compiled artifact, separates clear values from shares, and executes builtins and runtime hooks. | | MPC runtime | coordinator / parties / outputs | Runs secret-shared execution and opens or sends only the outputs the program authorizes. | ## Choose a path | If you want to... | Start here | | --------------------------------- | --------------------------------------------------------- | | Install Stoffel | [Installation](./getting-started/installation) | | Create and run a project | [Quick start](./getting-started/quick-start) | | Learn the daily CLI loop | [Basic usage](./getting-started/basic-usage) | | Build a first private computation | [Your first MPC project](./getting-started/first-project) | | Call Stoffel from an app | [Rust SDK overview](./rust-sdk/overview) | | Understand the runtime layers | [System architecture](./architecture/system) | ## Related reading * [Data in Use Protection: How MPC Keeps Inputs Hidden from the Cloud](https://stoffelmpc.com/stoffel-blog/mpc-data-in-use): a longer explanation of the compute-time privacy gap MPC is designed to close. * [Is Your Analytics Platform a Security Liability? What MXP 2026 Taught Me](https://stoffelmpc.com/stoffel-blog/what-mxp-2026-taught-me): a product example of how centralizing sensitive behavioral data creates exposure. # What is Multi-Party Computation? Source: https://docs.stoffelmpc.com/introduction/what-is-mpc A developer mental model for MPC: plaintext becomes shares, parties compute over shares, and only explicit outputs become visible. Multi-Party Computation (MPC) lets several parties compute a result without first collecting every private input in one place. For an application developer, the important shift is the data form. In a usual stack, sensitive values are decrypted into ordinary application state so code can run. In an MPC stack, sensitive values are transformed into shares before they cross into the computation. MPC parties compute over those shares, exchange protocol messages, and reveal only the output the program explicitly opens or returns to the client. ## What MPC changes MPC changes where plaintext exists during computation. A normal application flow often looks like this: ```text theme={null} user data -> decrypt to compute -> plaintext app state -> result ``` That plaintext app state can then touch logs, databases, analytics tools, support systems, queues, caches, and downstream services unless each layer is carefully constrained. An MPC flow is different: ```text theme={null} user data -> shares -> MPC parties compute over shares -> authorized output ``` The parties running the computation do not receive the full plaintext input. They receive shares: pieces of the private value that are useful to the protocol but not meaningful on their own. MPC privacy flow showing input owners splitting private values into shares, MPC parties computing over shares, and only the opened result returning. ## The core vocabulary | Term | Meaning | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | Plaintext value | The original value before it is protected. In a Stoffel application, this should stay at the app/client edge. | | Share | A protocol value derived from plaintext. A single share does not reveal the full input by itself. | | MPC party | A node that runs the protocol over shares. Parties hold shares, not complete plaintext inputs. | | Threshold | The number of shares or parties needed for a protocol action, such as reconstructing or opening a value. | | Protocol messages | Network messages parties exchange to evaluate operations that cannot be computed locally on one share. | | Opening | The moment a secret-shared value is intentionally reconstructed or returned as an output. | | Authorized output | A value the program is designed to reveal, either as an opened result or as output material returned to the client. | If you keep those nouns straight, the rest of MPC becomes easier to reason about: what exists as plaintext, what exists as shares, which nodes hold which material, and where values are allowed to become visible. ## What MPC parties see An MPC party sees: * its own share of each private input; * the compiled computation it is supposed to run; * protocol messages from other parties; * output shares or opened values that the program authorizes. An MPC party should not see the full private input just because it participates in the computation. That is the core security boundary. MPC is not magic privacy dust around an ordinary app server. It is a different execution model: move the sensitive part of the computation into a protocol where parties work over shares. ## What computation over shares means Some operations over shares are straightforward. For example, adding two shared values can often be represented as each party adding its local shares. Other operations require interaction. Multiplication, comparisons, conditionals over secret values, and many higher-level operations need protocol support. Parties exchange messages that let the computation advance without revealing the underlying plaintext inputs. A useful mental model: ```text theme={null} share-local operations: parties update their own shares interactive operations: parties exchange protocol messages openings / outputs: selected values become visible by design ``` This distinction matters for developers because private computation is not just normal code with encryption sprinkled on top. The shape of the program affects what the protocol has to do. For a deeper explanation of why multiplication needs protocol support, read [Computing with Secret Shares - Introducing Beaver Triples](https://stoffelmpc.com/stoffel-blog/beaver-triples-tuples). ## Openings: where secrecy intentionally ends An opening is not a bug. It is the point where the program intentionally turns a shared value into something visible. Good MPC program design is explicit about outputs: * Which result should become visible? * Who should receive it? * Is the output an opened value, output shares, or a client-consumable result? * Does the output itself reveal more than the application intends? MPC protects inputs during computation. It does not automatically make every possible output safe. The application still needs to decide what should be revealed. ## What this means in Stoffel Stoffel packages this model into a developer workflow: | MPC concept | Stoffel surface | | ----------------- | ---------------------------------------------------------- | | Computation | StoffelLang source (`.stfl`) | | Build artifact | `.stflb` bytecode | | Local development | `stoffel check`, `stoffel build`, and local MPC runs | | Parties | Local or networked Stoffel VM parties | | Private values | Shares passed through the MPC runtime | | Outputs | Explicit openings or client outputs defined by the program | The goal is not to hide every MPC concept. You still need to understand shares, parties, thresholds, and openings. Stoffel makes those concepts concrete in a compiler, VM, CLI, and Rust SDK instead of making every application team implement protocols from scratch. ## What MPC does not remove MPC narrows where sensitive plaintext exists during computation. It does not remove every security or product-design responsibility. You still need to design: * which values are private inputs; * which values are allowed to become outputs; * how outputs are delivered to the right client or system; * what metadata, timing, access patterns, logs, and deployment details may reveal; * which parties, thresholds, and backend assumptions match the application. Use MPC to move the sensitive computation out of ordinary plaintext application state. Then design the rest of the system around that boundary. ## Related reading * [Why should I care about Multiparty Computation?](https://stoffelmpc.com/stoffel-blog/intro-to-mpc): the broader case for MPC as a privacy-first application architecture. * [Introduction to Secret Sharing from First Principles](https://stoffelmpc.com/stoffel-blog/guide-to-secret-sharing): build intuition for shares, thresholds, and reconstruction. * [Computing with Secret Shares - Introducing Beaver Triples](https://stoffelmpc.com/stoffel-blog/beaver-triples-tuples): why private multiplication uses protocol support such as Beaver triples. ## Next steps * [Why Stoffel?](./why-stoffel): how Stoffel turns MPC concepts into a development workflow. * [Quick start](../getting-started/quick-start): create a project, build bytecode, and run local MPC. * [System architecture](../architecture/system): see how app code, bytecode, VM parties, and MPC backends fit together. # Why Stoffel? Source: https://docs.stoffelmpc.com/introduction/why-stoffel Why Stoffel exists as a developer workflow for building applications with secure multiparty computation. Secure multiparty computation (MPC) gives you a way to compute over private inputs without first pooling those inputs in one plaintext service. Stoffel gives you a way to build that private computation into an application without starting from protocol papers, custom networking, and one-off runtime code. The goal is not to hide every MPC concept. A Stoffel developer still needs to understand shares, parties, thresholds, protocol messages, and openings. Stoffel makes those concepts part of a compiler, bytecode format, VM, CLI, and Rust SDK so they can fit into a normal development loop. Comparison of a traditional MPC project path with a Stoffel project path, showing how Stoffel keeps application iteration visible while protocol plumbing stays packaged. ## The gap Stoffel closes Raw MPC development usually combines several hard problems at once: * designing the private computation; * choosing and integrating a protocol backend; * representing secret-shared values correctly; * coordinating parties and thresholds; * deciding where values are allowed to open; * connecting the private computation back to application code. Stoffel separates those concerns into developer-facing surfaces: | Concern | Stoffel surface | | ----------------------- | ---------------------------------------------------- | | Private computation | StoffelLang source (`.stfl`) | | Build artifact | `.stflb` bytecode | | Local feedback | `stoffel check`, `stoffel build`, and local MPC runs | | Runtime execution | Stoffel VM parties and MPC backends | | Application integration | Rust SDK and generated bindings | | Output boundary | explicit openings or client outputs | That separation is the main reason to use Stoffel. It gives the private part of the application a buildable artifact and a repeatable local workflow. ## What Stoffel gives you Stoffel packages the MPC workflow into a toolchain rather than a collection of protocol fragments. Stoffel toolchain diagram showing StoffelLang, the CLI, bytecode artifacts, VM parties, and Rust SDK integration connected by one bytecode contract. ### A language boundary for private computation You write the private workflow in StoffelLang instead of embedding protocol logic directly into the host application. That keeps the sensitive computation easier to review: inputs, share operations, and outputs live in a program designed for the MPC runtime. ### A compiled artifact The compiler produces `.stflb` bytecode. That artifact becomes the contract between the private computation and the surrounding application: what the program expects, what it can run, and where outputs are defined. ### A local development loop The CLI gives you an ordinary loop before deployment work begins: ```bash theme={null} stoffel check stoffel build stoffel run --parties 5 --threshold 1 ``` The point of local MPC is not to prove the full deployment is safe. It gives developers a fast way to catch language, build, integration, and runtime assumptions before coordinating real infrastructure. ### An application integration path The Rust SDK connects application code to Stoffel programs. The application still owns product logic, authentication, authorization, storage, client behavior, and output delivery. Stoffel owns the private-computation surface: bytecode, VM execution, party configuration, and secret-shared runtime values. Privacy boundary comparison showing a conventional backend receiving plaintext inputs versus a Stoffel-backed workflow with client shares, MPC parties, and authorized output boundaries. ## What remains explicit Stoffel is not “MPC without MPC concepts.” It is a developer workflow for MPC concepts. You still make explicit decisions about: * which inputs are private; * which values become shares; * how many parties run the computation; * what threshold assumptions the deployment uses; * which backend fits the computation; * which values open; * who receives the output; * what metadata, logs, timing, and access patterns may reveal outside the MPC protocol. Those decisions stay visible because hiding them would make the application harder to reason about. Stoffel’s job is to make them programmable and testable, not to make them disappear. ## When Stoffel is a good fit Stoffel is a good fit when the application needs to compute over sensitive values and you do not want those values to become ordinary plaintext backend state. Good early use cases usually have: * a clear private computation boundary; * inputs that can be modeled as private values or shares; * outputs that can be named and reviewed explicitly; * a team that wants local iteration before deployment planning; * Rust application code that needs to call into the private workflow. In those cases, Stoffel helps turn the privacy requirement into a concrete development path: write the private computation, compile it, run it locally, integrate it, then validate the deployment assumptions. ## When Stoffel may not be the right fit Stoffel may be the wrong starting point if: * the application only needs encryption at rest or in transit; * the sensitive data never needs to be computed on; * the required output would reveal the private input anyway; * the team cannot define who should learn the result; * the problem is mainly access control, logging, or data retention rather than compute-time plaintext exposure; * the deployment cannot support multiple MPC parties or the needed coordination model. This page should help you decide whether to try the workflow, not convince every project to use MPC. ## Try the workflow If the boundary fits your application, the next useful step is hands-on: 1. Install the `stoffel` CLI. 2. Create a project. 3. Check and build a `.stfl` program. 4. Run local MPC. 5. Connect the program from Rust. Start with [Installation](../getting-started/installation), then continue to [Quick start](../getting-started/quick-start). When you want the first app-shaped Rust project, run the [Rust SDK quickstart](../tutorials/rust-sdk-quickstart). For the runtime layers, read [System architecture](../architecture/system). ## Related reading * [Data in Use Protection: How MPC Keeps Inputs Hidden from the Cloud](https://stoffelmpc.com/stoffel-blog/mpc-data-in-use): how MPC changes the usual backend trust boundary. * [Is Your Analytics Platform a Security Liability? What MXP 2026 Taught Me](https://stoffelmpc.com/stoffel-blog/what-mxp-2026-taught-me): a product-focused look at why analytics systems create exposure when they centralize behavioral data. # AVSS Source: https://docs.stoffelmpc.com/mpc-protocols/avss AVSS backend details for Feldman commitments, verifiable scalar shares, and curve-compatible workflows in Stoffel. AVSS is Stoffel's backend for group/scalar-oriented MPC workflows. Use it when secret values are cryptographic scalars that need verifiable shares, public commitments, or curve-compatible outputs. AVSS stands for asynchronous verifiable secret sharing. In Stoffel, the AVSS path uses Feldman-style commitments: shares are scalar values, commitments are group elements, and parties can check that a share is consistent with the committed polynomial under the discrete-log assumption. ## What it is good for AVSS is the right backend when the secret value is a cryptographic scalar whose share needs a public commitment or curve-compatible output: * the program exposes public commitments to secret shares; * the backend must use a curve selected by an external verifier or protocol; * public transcript bytes must become curve-field challenges; * the outside system consumes a commitment, curve-encoded value, opened scalar response, or signature-related artifact. For ordinary private arithmetic over integers or fixed-point values, start with [HoneyBadgerMPC](./honeybadger-mpc). ## Protocol model AVSS uses a dealer/share/commitment model: 1. a secret scalar is shared as evaluations of a polynomial; 2. the dealer publishes commitments to the polynomial coefficients; 3. each party verifies its share against those commitments; 4. later, enough valid shares can reconstruct or derive the intended cryptographic value. For an elliptic curve with base point `G`, the commitment form is conceptually: ```text theme={null} C_j = a_j * G ``` where `a_j` is a polynomial coefficient. A party can verify a scalar share against the public commitment points without learning the underlying secret scalar. ## Security posture AVSS commitments are public group elements tied to the shared scalar. That is useful when another system needs to verify a commitment, curve-encoded value, or scalar response, but it is usually the wrong privacy boundary for ordinary application data. If the value is app data and the goal is private computation over integers or fixed-point values, start with HoneyBadgerMPC. Cryptographic note: Feldman-style commitments are binding under discrete-log hardness, but they are not hiding commitments. The developer consequence is that AVSS commitments are verifier-facing artifacts, not a drop-in privacy boundary for low-entropy values. Keep these distinctions in mind: * AVSS is about verifiable scalar shares and group commitments. * Feldman-style commitments expose public group elements tied to the secret polynomial. * Low-entropy application values should not be treated like random cryptographic scalars. * Full threshold signing protocols may need additional logic beyond share generation and commitment access. * Stoffel's current local/network config enforces `parties >= 4 * threshold + 1`. ## Curves Stoffel accepts AVSS curve selectors through the CLI and Rust SDK: | Curve selector | Rust enum | | --------------------- | ------------------- | | `bls12_381` | `Curve::Bls12_381` | | `bn254` | `Curve::Bn254` | | `curve25519` | `Curve::Curve25519` | | `ed25519` | `Curve::Ed25519` | | `secp256k1` | `Curve::Secp256k1` | | `p-256` / `secp256r1` | `Curve::Secp256r1` | Use the curve required by the external verifier or protocol boundary. Curve selectors are not interchangeable when another system needs to verify a public commitment, encoded group element, scalar response, or signature-related artifact. ## Configure AVSS `avss` defaults to `bls12_381`: ```toml theme={null} [mpc] backend = "avss" parties = 5 threshold = 1 ``` Use an explicit curve for curve-compatible workflows: ```toml theme={null} [mpc] backend = "avss:secp256k1" parties = 5 threshold = 1 ``` Or set the curve separately: ```toml theme={null} [mpc] backend = "avss" curve = "ed25519" parties = 5 threshold = 1 ``` CLI override: ```bash theme={null} stoffel build --backend avss --field secp256k1 --parties 5 --threshold 1 stoffel run --backend avss:ed25519 --parties 5 --threshold 1 ``` Rust SDK: ```rust theme={null} use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let config = MpcConfig::builder() .parties(5) .threshold(1) .avss(Curve::Secp256k1) .build()?; # Ok(()) # } ``` Program builder: ```rust theme={null} # use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/threshold-demo.stflb")? .parties(5) .threshold(1) .backend(MpcBackend::Avss { curve: Curve::Ed25519 }) .execute_local() .await?; # Ok(()) # } ``` The Rust SDK's off-chain client I/O path currently supports AVSS client I/O over `bls12_381`. Other AVSS curves are available to the bytecode/runtime path, local protocol work, and StoffelLang examples where the selected curve is handled by the VM/backend. ## Circuit-shaping for AVSS AVSS is the right choice when the application needs verifiable scalar shares, group commitments, or curve-compatible threshold-cryptography building blocks. The optimization target is the cryptographic transcript: keep public transcript work public, choose the curve for the external protocol, use commitments deliberately, and minimize unnecessary openings. See [Performance and circuit shaping](./performance-and-circuit-shaping#avss-transcript-shaping-intuition) for the full checklist. ## StoffelLang AVSS helpers StoffelLang includes an `AvssShare` opaque type and AVSS helper methods: ```stoffel theme={null} builtin opaque AvssShare builtin object Avss: def get_commitment(share: AvssShare, index: int64) -> bytes {.builtin.}: def get_key_name(share: AvssShare) -> string {.builtin.}: def commitment_count(share: AvssShare) -> int64 {.builtin.}: def is_avss_share[T](value: T) -> bool {.builtin.}: ``` Secret shares can also expose commitments through Share methods when the backend produced committed share data: ```stoffel theme={null} def main() -> bytes: var secret_key: secret int64 = Share.random() return secret_key.get_commitment(0) ``` Threshold-signature examples live in the StoffelLang examples tree: ```text theme={null} crates/stoffel-lang/examples/threshold_signatures/ crates/stoffel-lang/examples/avss_certificate/ crates/stoffel-lang/examples/avss_share_auditor/ ``` ## Use-case examples AVSS is the best fit when the secret is a cryptographic scalar and the application needs public group commitments, curve-specific encodings, or threshold-signature building blocks. The examples below use Stoffel features that are specific to those workflows: persisted secret shares, `Mpc.curve()`, commitment extraction, curve encodings, transcript hashing, and client-output shares. ### Persistent threshold key material Use this shape when MPC parties need to generate a signing key once, persist each party's share, and publish the corresponding curve point as the application-facing public key: ```stoffel theme={null} def main() -> bytes: var storage_key = "ca:avss:sk:v1" if LocalStorage.exists(storage_key): discard LocalStorage.load_share(storage_key) else: var generated_key: secret int64 = Share.random() discard LocalStorage.store(storage_key, generated_key) var secret_key: secret int64 = LocalStorage.load(storage_key) var public_key = secret_key.get_commitment(0) return Crypto.point_to_sec1(public_key, Mpc.curve()) ``` This uses the AVSS/Feldman distinction directly: the scalar key remains shared and persisted per party, while the public commitment point can be encoded for the selected curve and returned to the application. ### Threshold signature response with client output Use this shape when a signing workflow needs committed nonces, curve-specific challenge hashing, and selected signature shares returned to a client: ```stoffel theme={null} def main() -> bytes: var client_count = ClientStore.get_number_clients() var secret_key: secret int64 = Share.random() var public_key = secret_key.get_commitment(0) var nonce: secret int64 = Share.random() var nonce_commitment = nonce.get_commitment(0) var message = Bytes.from_string("threshold message") var challenge_input = Bytes.concat(nonce_commitment, message) var challenge_hash = Crypto.sha256(challenge_input) var challenge = Crypto.hash_to_field(challenge_hash, Mpc.curve()) if client_count > 0: var challenge_share = ClientStore.take_share(0, 0) challenge = challenge_share.open_field() var response_share = nonce.add(secret_key.mul_field(challenge)) var response = response_share.open_field() if client_count > 0 and Mpc.has_capability("client-output"): var output_shares: list[Share] = [response_share] MpcOutput.send_to_client(0, output_shares) var signature = Bytes.concat(nonce_commitment, response) return Bytes.concat(signature, Crypto.point_to_sec1(public_key, Mpc.curve())) ``` This is not a complete production signing protocol by itself. It shows the AVSS-oriented boundary: generate scalar shares, expose nonce/key commitments as curve points, hash transcript data into the selected curve field, and route the response share through Stoffel's client-output channel. ## Further reading * [Performance and circuit shaping](./performance-and-circuit-shaping) * Feldman, “A Practical Scheme for Non-interactive Verifiable Secret Sharing”: [https://www.cs.umd.edu/\~gasarch/TOPICS/secretsharing/feldmanVSS.pdf](https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf) * Cachin, Kursawe, Lysyanskaya, Strobl, “Asynchronous Verifiable Secret Sharing and Proactive Cryptosystems”: [https://doi.org/10.1145/586110.586122](https://doi.org/10.1145/586110.586122) * Gennaro, Jarecki, Krawczyk, Rabin, “Secure Distributed Key Generation for Discrete-Log Based Cryptosystems”: [https://doi.org/10.1007/3-540-48910-X\_21](https://doi.org/10.1007/3-540-48910-X_21) * FROST threshold Schnorr signatures: [https://eprint.iacr.org/2020/852](https://eprint.iacr.org/2020/852) # HoneyBadgerMPC Source: https://docs.stoffelmpc.com/mpc-protocols/honeybadger-mpc HoneyBadgerMPC backend details for asynchronous, robust, field-based MPC in Stoffel. HoneyBadgerMPC is Stoffel's default MPC backend. Use it when secret application values are represented as field-compatible shares and the outside system consumes an opened result or client-output shares from private computation. It is an asynchronous, robust MPC protocol family. In practical terms, Stoffel can run a known set of MPC parties without relying on a fixed network round clock, while tolerating Byzantine parties up to the configured threshold. ## What it is good for HoneyBadgerMPC is the right starting point when the secret values are application data represented as field-compatible shares: * the program computes over secret integers, fixed-point values, or other field-compatible values; * public parameters can stay public while private inputs remain ordinary MPC shares; * the main cost questions are multiplication count, multiplication depth, comparisons, and reveal boundaries; * the output boundary is an opened value or client-output shares. It is less natural when the output boundary is a public commitment, curve-encoded value, opened scalar response, or signature-related artifact. Use AVSS when the secret value is a cryptographic scalar that must match an external curve or verifier. ## Protocol model HoneyBadgerMPC-style protocols are built around finite-field secret sharing: * secrets are encoded as field elements; * parties hold shares of those field elements; * addition and subtraction are local operations on shares; * multiplication consumes preprocessed material such as Beaver triples; * opening/revealing reconstructs a clear value from enough valid shares. The sharing model is closely related to Shamir secret sharing and Reed-Solomon/error-correcting-code ideas: polynomial evaluations are shares, and interpolation/reconstruction can detect or tolerate faulty shares under the protocol threshold. ## Security posture For developer configuration, remember three points: 1. HoneyBadgerMPC is designed for asynchronous, Byzantine-robust MPC. 2. HoneyBadgerMPC is the default path for private computation over application values; concrete deployments still rely on ordinary cryptography for transport, authentication, signatures, and implementation details. 3. Stoffel's current local/network config enforces `parties >= 4 * threshold + 1`. That last rule is what developers should use when choosing local or network topology values, even though the underlying asynchronous MPC literature often starts from `n >= 3t + 1` thresholds. ## Configure HoneyBadgerMPC `honeybadger` is the default backend. ```toml theme={null} [mpc] backend = "honeybadger" parties = 5 threshold = 1 ``` CLI override: ```bash theme={null} stoffel build --backend honeybadger --parties 5 --threshold 1 stoffel run --backend honeybadger --client-input 0=42 --parties 5 --threshold 1 ``` Rust SDK: ```rust theme={null} use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let config = MpcConfig::builder() .parties(5) .threshold(1) .honeybadger() .build()?; # Ok(()) # } ``` Program builder: ```rust theme={null} # use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .backend(MpcBackend::HoneyBadger) .with_client_input(0, &[42_i64]) .execute_local() .await?; # Ok(()) # } ``` ## Preprocessing Multiplication of two secret shares needs preprocessed material. Stoffel records preprocessing demand in the bytecode manifest and exposes preprocessing configuration in the Rust SDK network builders. ```rust theme={null} # use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::HoneyBadger) .preprocessing(1000, 500) .build()?; # Ok(()) # } ``` Local CLI/SDK paths hide most of this setup. If a program consumes more multiplication/randomness material than the run prepared, increase preprocessing configuration or simplify the circuit. For advanced MPC workloads, use the preprocessing warning as a signal to inspect multiplication count, multiplication depth, type choices, and reveal boundaries. See [Performance and circuit shaping](./performance-and-circuit-shaping#honeybadgermpc-preprocessing-intuition) for the full checklist. ## Operation costs | Operation | Cost shape | | ------------------------------- | --------------------------------------------------------------------------- | | Secret addition/subtraction | Local share operation. | | Secret plus/minus public scalar | Local share operation. | | Secret multiplication | Consumes multiplication preprocessing and requires protocol work. | | Opening/reveal | Requires enough parties to reconstruct the value. | | Comparisons/bit-heavy logic | Encoded into field operations and generally more expensive than arithmetic. | ## Use-case examples HoneyBadgerMPC is the best fit when the application is mostly field arithmetic over private inputs. The protocol's share representation makes additions and scalar shifts local, while multiplication uses the backend's preprocessed multiplication material. The examples below use Stoffel features that matter in real applications: typed fixed-point shares, loops, variable client counts, client-output channels, and explicit reveal boundaries. ### Federated fixed-point aggregation Use this shape when clients contribute model updates, risk vectors, or analytics rows and the application needs per-position aggregates without revealing individual contributions: ```stoffel theme={null} def federated_average_2x3() -> list[fix64]: var matrix_size: int64 = 6 var num_clients: int64 = ClientStore.get_number_clients() var result: list[fix64] = [] var output_shares: list[Share] = [] var element_index: int64 = 0 while element_index < matrix_size: var element_sum = ClientStore.take_share_fixed(0, element_index) var client_index: int64 = 1 while client_index < num_clients: var share = ClientStore.take_share_fixed(client_index, element_index) element_sum = element_sum.add(share) client_index = client_index + 1 var revealed_sum: fix64 = element_sum.open_fixed() result.append(revealed_sum / num_clients) output_shares.append(element_sum) element_index = element_index + 1 if num_clients > 0 and Mpc.has_capability("client-output"): MpcOutput.send_to_client(0, output_shares) return result def main() -> list[fix64]: return federated_average_2x3() ``` This uses HoneyBadgerMPC for a field-heavy workload: each matrix element is summed as a fixed-point share, the aggregate can be routed back to an output client as shares, and only the average values selected by the program are opened. ### Private score routing with client-specific outputs Use this shape when the host application should learn only coordination metadata while selected clients receive private result shares: ```stoffel theme={null} def normalize_score(raw_score: Share) -> Share: var adjusted = raw_score.add_scalar(50) return adjusted.mul_scalar(2) def main() -> int64: var client_count = ClientStore.get_number_clients() var income_score = ClientStore.take_share(0, 0) var eligibility = normalize_score(income_score) if client_count > 1: var coapplicant_score = ClientStore.take_share(1, 0) eligibility = eligibility.add(coapplicant_score) if Mpc.has_capability("client-output"): var client_outputs: list[Share] = [] client_outputs.append(eligibility) MpcOutput.send_to_client(0, client_outputs) if client_count > 1: eligibility.send_to_client(1) return client_count ``` This keeps the score as a share instead of opening it to the host application. HoneyBadgerMPC is doing the general private arithmetic, while Stoffel's `ClientStore`, `Mpc.has_capability`, and `MpcOutput` APIs define which clients supply and receive private values. ## Further reading * [Performance and circuit shaping](./performance-and-circuit-shaping) * Andrew Miller et al., “HoneyBadgerMPC and AsynchroMix: Practical Asynchronous MPC and its Application to Anonymous Communication”: [https://eprint.iacr.org/2019/883](https://eprint.iacr.org/2019/883) * Ben-Or, Canetti, Goldreich, “Asynchronous Secure Computation”: [https://doi.org/10.1145/167088.167109](https://doi.org/10.1145/167088.167109) * Shamir, “How to Share a Secret”: [https://doi.org/10.1145/359168.359176](https://doi.org/10.1145/359168.359176) # How Backend Selection Works Source: https://docs.stoffelmpc.com/mpc-protocols/implementation How Stoffel carries MPC backend selection through config, bytecode, the VM, local runs, and SDK network/client configuration. Use this page after choosing a backend to see how that choice flows through `Stoffel.toml`, CLI flags, Rust SDK builders, bytecode metadata, local execution, and network/client configuration. If you need the app-level input, execution, and output flow, use [MPC Integration](../architecture/mpc). For backend selection guidance, start with [MPC Backends](./overview). Stoffel records the selected MPC backend in the compiled program and carries it through the CLI, Rust SDK, VM, and network/client layers. ## End-to-end path HoneyBadger MPC runtime diagram showing client input/output paths, coordinator-managed sessions, preprocessing material, and peer protocol rounds between VM parties. Campaign-style networked privacy backend diagram showing client input and output paths, coordinator routing, preprocessing, and peer protocol messages. At runtime, clients submit protected inputs, the coordinator manages session lifecycle and IO routing, and the parties execute the VM while exchanging HoneyBadger protocol messages. The important invariant is that the compiled bytecode and the runtime agree on: * backend: HoneyBadgerMPC or AVSS; * curve/field where applicable; * parties and threshold; * client input/output schemas; * preprocessing demand for operations such as multiplication and randomness. ## Backend selectors At the SDK level, backend selection is represented as: ```rust theme={null} MpcBackend::HoneyBadger MpcBackend::Avss { curve: Curve::Bls12_381 /* or another Curve */ } ``` At the CLI/config level, the accepted string selectors are: ```text theme={null} honeybadger avss avss:bls12_381 avss:bn254 avss:curve25519 avss:ed25519 avss:secp256k1 avss:p-256 ``` `--field` / `curve = "..."` sets the AVSS curve. HoneyBadgerMPC does not take a curve selector in the SDK config; it uses the field configuration expected by the HoneyBadger path. ## Current config validation Stoffel validates local and network MPC topology with: ```text theme={null} parties >= 4 * threshold + 1 parties >= 5 threshold > 0 ``` This is the rule developers should use for project config, local runs, and network config. It is stricter than the baseline `3t + 1` threshold often used to introduce asynchronous Byzantine protocols because Stoffel's current end-to-end path includes preprocessing and robust execution requirements. SDK summaries also expose the minimum reconstruction shares: | Backend | Reported reconstruction threshold | | -------------- | --------------------------------- | | HoneyBadgerMPC | `2 * threshold + 1` | | AVSS | `threshold + 1` | ## Bytecode manifest The `.stflb` manifest stores backend metadata so a runtime can reject mismatched execution settings early. ```text theme={null} CompiledBinary └── ClientIoManifest ├── mpc_backend: HoneyBadger | Avss ├── mpc_curve: Bls12_381 | Bn254 | Curve25519 | Ed25519 | Secp256k1 | Secp256r1 ├── clients: input/output share schemas └── preprocessing_demand ``` StoffelLang compilation writes this metadata from compiler options. The CLI and SDK set those options from project config or builder overrides. ## Local MPC execution `stoffel run`, `stoffel dev`, and SDK `.execute_local().await?` run the compiled program against a local MPC test network on the developer machine. The local runner receives: * the compiled program; * entrypoint name; * backend kind; * curve config; * parties and threshold; * ClientStore inputs and expected output-client metadata. This lets the same source program be checked against HoneyBadgerMPC or AVSS by changing backend configuration instead of changing application code. ## Network and off-chain client execution The Rust SDK can generate network deployment configs and build client/server handles. Backend selection is part of those configs: ```rust theme={null} # use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::HoneyBadger) .preprocessing(1000, 500) .build()?; # Ok(()) # } ``` AVSS can be selected in the same builder: ```rust theme={null} # use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::Avss { curve: Curve::Bls12_381 }) .preprocessing(1000, 500) .build()?; # Ok(()) # } ``` The current SDK off-chain client I/O path supports HoneyBadgerMPC and AVSS over `bls12_381`. Other AVSS curves are still selectable for bytecode/runtime paths and curve-aware StoffelLang protocol examples; validate the specific client/network path you plan to use. ## VM boundary The VM does not expose different application syntax for each backend. Secret-register operations and builtins yield backend-specific work through the MPC runtime: | VM operation | Backend effect | | ----------------------------------- | -------------------------------------------------------------------- | | clear-to-secret input | Creates backend share data. | | secret multiplication | Uses backend multiplication/preprocessing path. | | opening/reveal | Reconstructs according to backend share type. | | `Share.random` / `Share.random_int` | Produces backend random share data. | | `Share.get_commitment` | Returns commitment bytes when the backend share carries commitments. | | `Avss.*` helpers | Inspect AVSS share metadata and commitments. | This separation lets developers write StoffelLang around `secret T`, `Share`, `ClientStore`, `Mpc`, and `MpcOutput` while selecting the backend in config. ## Backend-specific notes ### HoneyBadgerMPC HoneyBadgerMPC share data is field-oriented and supports the general arithmetic path. Its local/network execution path uses robust field-share reconstruction and preprocessing for multiplication-heavy workloads. ### AVSS AVSS share data can carry Feldman commitment material. StoffelLang exposes `AvssShare` helpers and `Share.get_commitment(...)` when the output boundary needs public commitments, curve-encoded values, or scalar responses. Curve selection matters because scalar-field and group encodings must match the external verifier or protocol boundary. ## Troubleshooting | Symptom | Check | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Config says the threshold is invalid | Confirm `parties >= 4 * threshold + 1` and `parties >= 5`. | | HoneyBadger rejects a curve selector | Use `backend = "honeybadger"` without a curve, or switch to AVSS for curve workflows. | | AVSS client I/O fails on a non-BLS curve | The current SDK off-chain client I/O path supports AVSS over `bls12_381`; test other curves through supported local/runtime paths. | | Runtime backend mismatch | Rebuild `.stflb` after changing `[mpc]` config or pass matching SDK/CLI backend overrides. | | Multiplication exhausts preprocessing | Increase preprocessing in network config or reduce secret multiplications. | ## See also * [MPC Protocols](./overview) * [HoneyBadgerMPC](./honeybadger-mpc) * [AVSS](./avss) * [Stoffel VM builtins](../stoffel-vm/builtins) * [Rust SDK API](../rust-sdk/api) # MPC Backends Source: https://docs.stoffelmpc.com/mpc-protocols/overview Choose the Stoffel MPC backend that matches your program's secret value representation, cost model, and verifier-facing outputs. Stoffel separates application code from the MPC protocol used to run it. The selected backend determines how secret values are shared, computed over, and reconstructed across MPC parties. Stoffel currently supports two asynchronous, robust MPC backend families: | Backend | Selector | Use when | Main design lens | | -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | HoneyBadgerMPC | `honeybadger` | Your program is mostly private field arithmetic over application values. | Circuit shape: multiplication count, multiplication depth, type choices, reveals, and preprocessing capacity. | | AVSS | `avss` or `avss:` | Your program needs committed scalar shares, public commitments, curve-compatible outputs, or threshold-cryptography building blocks. | Protocol transcript: curve selection, commitments, transcript-to-field boundaries, openings, and verifier-facing artifacts. | Both backends are designed for asynchronous/robust settings: parties do not rely on a fixed network round clock, and the run can tolerate Byzantine parties up to the configured threshold. The backend choice determines which share representation, curve/field, preprocessing, and client I/O path Stoffel records in the bytecode manifest. `mpc-protocols` has received an external audit from Zellic. Audit scope matters: validate the specific protocol version, integration code, deployment configuration, and threat model you rely on. Other Stoffel components have also had AI-assisted security review, including tools such as veria.dev. Treat audit and review coverage as component-scoped when evaluating a deployment. ## Choose by workload shape Start from the shape of the secret work, not the name of the application. MPC privacy flow showing private inputs split into shares, computation across MPC parties, and explicit output reconstruction. Select AVSS with the default curve: Campaign-style networked privacy backend diagram showing how clients, the coordinator, preprocessing, and VM parties fit together for MPC execution. ### Overview HoneyBadger is an asynchronous Byzantine fault-tolerant (BFT) MPC protocol that: * **Tolerates malicious parties**: Up to `t` parties can be actively malicious * **Works asynchronously**: No timing assumptions required * **Provides guaranteed output delivery**: Honest parties always get results ### Security Guarantees | Property | Description | | --------------------- | -------------------------------------------------------------- | | **Privacy** | No coalition of ≤t parties learns anything about honest inputs | | **Correctness** | Output is always the correct result of the computation | | **Guaranteed Output** | Honest parties always receive their outputs | | **Fairness** | Either all honest parties get output, or none do | ### Configuration Constraints The number of parties `n` and threshold `t` must satisfy: ``` n >= 3t + 1 ``` Select AVSS with a specific curve: ```toml theme={null} [mpc] backend = "avss:secp256k1" parties = 5 threshold = 1 ``` You can also keep `backend = "avss"` and set the curve separately: ```toml theme={null} [mpc] backend = "avss" curve = "p-256" parties = 5 threshold = 1 ``` Accepted curve names include `bls12_381`, `bn254`, `curve25519`, `ed25519`, `secp256k1`, and `p-256`. ## Select a backend from the CLI ```bash theme={null} # Default field-MPC backend stoffel build --backend honeybadger --parties 5 --threshold 1 # AVSS default curve stoffel build --backend avss --parties 5 --threshold 1 # AVSS over an elliptic-curve scalar field stoffel build --backend avss --field secp256k1 --parties 5 --threshold 1 # Equivalent selector form stoffel build --backend avss:secp256k1 --parties 5 --threshold 1 ``` `stoffel check`, `stoffel compile`, `stoffel build`, `stoffel run`, and `stoffel dev` accept the same backend/field overrides when they compile source or project settings. ## Select a backend from Rust ```rust theme={null} use stoffel::prelude::*; # fn example() -> stoffel::Result<()> { let honeybadger = MpcConfig::builder() .parties(5) .threshold(1) .honeybadger() .build()?; let avss = MpcConfig::builder() .parties(5) .threshold(1) .avss(Curve::Secp256k1) .build()?; # Ok(()) # } ``` On program builders, use `.backend(...)` or `.curve(...)`: ```rust theme={null} # use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/threshold-demo.stflb")? .parties(5) .threshold(1) .backend(MpcBackend::Avss { curve: Curve::Secp256k1 }) .execute_local() .await?; # Ok(()) # } ``` Generated manifests can carry the bytecode backend and curve into SDK builders, so app code does not need to duplicate backend literals by hand. ## Current topology constraints Stoffel validates local and network MPC configs with the current robust-MPC threshold rule: ```text theme={null} parties >= 4 * threshold + 1 ``` That means the common developer default is five parties with threshold one. The reconstruction shape differs by backend: | Backend | Reconstruction shape | | -------------- | --------------------------------------------------------------------------------------------------------------- | | HoneyBadgerMPC | Uses robust field-share reconstruction; current SDK summaries report `2 * threshold + 1` reconstruction shares. | | AVSS | Uses Feldman-style share reconstruction; current SDK summaries report `threshold + 1` reconstruction shares. | The protocol literature often describes asynchronous Byzantine thresholds in terms of `n >= 3t + 1`. Stoffel's current configuration layer enforces the stricter `4t + 1` rule because the implemented local/network path includes preprocessing and robust execution requirements. ## Further reading * [Performance and circuit shaping](./performance-and-circuit-shaping) * [HoneyBadgerMPC](./honeybadger-mpc) * [AVSS](./avss) * [How backend selection works](./implementation) * [CLI overview](../cli/overview) * [Rust SDK API](../rust-sdk/api) # Performance and Circuit Shaping Source: https://docs.stoffelmpc.com/mpc-protocols/performance-and-circuit-shaping How to reason about secret-dependent work, preprocessing demand, type choices, and transcript shape across Stoffel MPC backends. Use this page after the basic backend decision is clear and the next question is how to shape the work that runs inside MPC. For backend selection, start with [MPC Backends](./overview). For config, bytecode, local runs, and network execution, use [How Backend Selection Works](./implementation). For backend-specific examples, see [HoneyBadgerMPC](./honeybadger-mpc) and [AVSS](./avss). This page focuses on the cost model behind those choices: what becomes secret-dependent work, which type choices change preprocessing demand, and how HoneyBadgerMPC circuit costs differ from AVSS transcript costs. ## Backend cost model | Question | HoneyBadgerMPC intuition | AVSS intuition | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | What tends to be expensive? | Secret × secret multiplication, comparisons, bit decomposition, and nonlinear functions. | Scalar-share operations that participate in threshold-signature/key workflows, openings, commitments, and curve-specific transcript steps. | | What is usually cheap? | Additions, subtractions, and share × clear-scalar operations. | Public transcript hashing, public curve encodings, and commitment handling once the committed share exists. | | What should you optimize first? | Multiplication count, multiplication depth, and the amount of preprocessing material needed for the largest input shape. | The protocol transcript: keep messages public where possible, avoid unnecessary openings, persist long-lived key shares, and avoid using AVSS for ordinary application data that only needs private computation. | ## Circuit-shaping checklist For both backends: * Count secret-dependent operations first. Public constants, hashes, encodings, and normalization factors should usually be computed outside the MPC circuit. * Distinguish total operation count from dependency depth. Independent work can often be batched or planned together; long dependent chains create extra interactive layers. * Prefer public parameters when they are not part of the private input. Public coefficients usually make a private weighted computation cheaper than secret coefficients. * Treat comparisons, bit-level logic, and nonlinear functions as expensive until measured. They often compile into more field operations than the source code suggests. * Size network/runtime configuration for the largest expected input shape, not the happy-path demo input. ## Type choices and preprocessing demand Types affect preprocessing demand because they influence which VM and share operations the compiler emits: * Clear/public values stay local. Public `int64`, `fix64`, `bytes`, curve names, constants, hashes, and encodings should remain clear until a secret operation actually needs them. * Secret values enter the MPC circuit. `secret int64`, `secret bool`, fixed-point shares, and `Share` values can consume preprocessing when an operation requires interaction. * Public weights are cheaper than secret weights. A private score with public coefficients is mostly share × clear-scalar work; making the coefficients secret turns those terms into secret × secret multiplications. * Fixed-point addition is usually close to integer share addition, but fixed-point multiplication can require extra scaling, truncation, or rounding logic. Use fixed-point where the application needs it, but avoid secret fixed-point products when a clear scaling factor is enough. * `secret bool`, comparisons, range checks, and bit decomposition are often larger than they look. A single source-level comparison can expand into many lower-level field operations. * Containers multiply the cost of their element operations. A `list[secret int64]` is not expensive by itself; a loop that multiplies each element by another secret value consumes work per element. * Width matters for low-level share helpers. Operations over 64-bit ranges generally require more bit work than the same operation over an 8- or 16-bit domain, so use the smallest safe width when writing explicit comparison or bit-decomposition code. For AVSS, the important type question is whether the value is really a cryptographic scalar that needs a public commitment or curve-compatible output. If the value is ordinary application data and the output boundary is an opened result or client-output shares, start with HoneyBadgerMPC unless the application specifically needs AVSS commitments or curve compatibility. ## HoneyBadgerMPC preprocessing intuition HoneyBadgerMPC programs consume preprocessing primarily when they multiply secret shares or execute operations that compile into secret multiplications. Use preprocessing warnings as a signal to inspect the circuit shape: * Count secret × secret multiplications first. Additions, subtractions, and share × clear-scalar operations are usually local share operations; they should not drive triple demand the way secret multiplication does. * Separate multiplication count from multiplication depth. More independent multiplications mainly require more preprocessing material. Long dependent chains also increase the number of interactive multiplication layers, so they affect latency as well as total demand. * Batch independent work. Prefer vector/matrix-style loops where independent products can be planned together, rather than serializing products through an accumulator when the math does not require it. * Move clear work out of the MPC circuit. Precompute public constants, lookup tables, hashes, encodings, and normalization factors before they become secret-dependent values. * Replace secret × secret products with share × clear-scalar operations when one side is public. For weighted computations, keep coefficients public when they are not part of the private input. * Be careful with comparisons, bit decomposition, and nonlinear functions. They compile into many field operations and often dominate preprocessing demand more than the top-level source code suggests. * If the circuit is already appropriate, increase preprocessing capacity in the network configuration. Treat `.preprocessing(multiplication_count, random_count)` as a budget for the backend material the run can consume, and size it with headroom for the largest expected input shape. ## AVSS transcript-shaping intuition AVSS uses scalar shares and public commitments. The optimization target is usually the cryptographic transcript, not a generic multiplication budget: * Keep public transcript work public. Message encoding, domain separators, curve names, public keys, and transcript hashes should usually stay as clear bytes/fields until a secret scalar operation actually needs them. * Use `Crypto.hash_to_field(..., Mpc.curve())` at the boundary where public transcript data becomes a curve-field challenge. Avoid turning public transcript material into secret shares earlier than necessary. * Persist long-lived key shares with `LocalStorage` instead of regenerating them on every run. For signing protocols, never persist or reuse per-signature nonces unless the protocol explicitly requires and protects that state. * Use commitments deliberately. `get_commitment(0)` gives the public group commitment associated with the scalar share; it is useful for public keys, nonce commitments, audit handles, and verification transcripts. * Minimize openings. Opening scalar responses or intermediate fields is sometimes part of a signature protocol, but unnecessary openings weaken privacy boundaries and add protocol work. * Choose the curve for the external protocol first. `ed25519`, `secp256k1`, `p-256`, `bn254`, and `bls12_381` are not interchangeable when another system needs to verify a public commitment, encoded group element, scalar response, or signature-related artifact. * Do not use AVSS as a generic replacement for private arithmetic over ordinary application data. If the output boundary is an opened result or client-output shares, HoneyBadgerMPC is usually the better starting point. ## AI-agent prompt shape The canonical AI-agent workflow lives in [Stoffel AI Agent Implementation](/developer-skills/stoffel-ai-agent-implementation). For performance-sensitive backend work, include the cost constraints below in the prompt. When using an AI coding agent, ask it to optimize around the same boundaries a human would inspect: ```text theme={null} Build the smallest StoffelLang program for this MPC task. Secret values: - ... Public values: - ... Input owners: - ... Output boundary: - opened value / client-output share / public commitment / curve-encoded artifact Backend: - honeybadger or avss: - reason for choosing it Cost constraints: - avoid secret × secret products unless required - keep public transcript/hash/encoding work public - use public weights/constants where possible - validate with `stoffel check` ``` For docs or examples, require the agent to run `stoffel check` on StoffelLang snippets and `npx mintlify validate` before reporting completion. ## See also * [MPC Backends](./overview) * [HoneyBadgerMPC](./honeybadger-mpc) * [AVSS](./avss) * [How backend selection works](./implementation) # API Reference Source: https://docs.stoffelmpc.com/python-sdk/api Current Python SDK status and the supported CLI/Rust SDK alternatives. Stoffel currently provides a Python project template, not a finalized Python SDK package API. Do not depend on design sketches such as `StoffelProgram`, `StoffelClient`, or `execute_with_inputs` as release APIs. ## Supported Python-oriented workflow Use the CLI template to scaffold a Python project that contains a Stoffel program: ```bash theme={null} stoffel init my-python-project --template python cd my-python-project stoffel check stoffel stoffel build stoffel ``` Run the nested Stoffel program through the CLI: ```bash theme={null} stoffel run stoffel --input a=40 --input b=2 stoffel run stoffel --client-input 0=42 --parties 5 --threshold 1 ``` Use the generated README and nested `Stoffel.toml` as the source of truth for the exact file names and inputs created by your installed CLI. ## Executable application API For applications that need to compile, load, or run Stoffel programs directly, use the Rust SDK: ```rust theme={null} use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; # Ok(()) # } ``` A Python service can shell out to the CLI around a nested Stoffel project, or call a Rust service/library boundary that uses the Rust SDK. ## Future Python package shape A future Python package should mirror the same concepts as the CLI and Rust SDK: * loading `.stflb` bytecode; * passing named inputs and ClientStore slot inputs; * running local MPC development flows; * connecting to network configurations for deployed MPC nodes; * keeping private input boundaries explicit. Until that package is finalized, treat Python API examples as design notes only. ## See also * [Python SDK Overview](./overview) * [Rust SDK API](../rust-sdk/api) * [CLI Overview](../cli/overview) # Examples Source: https://docs.stoffelmpc.com/python-sdk/examples Python template examples and current alternatives for Stoffel 0.1.0. **Status: Python template examples** Stoffel 0.1.0 provides a Python project template, not a finalized Python SDK package. Use the Rust SDK examples for verified end-to-end execution. ## Create a Python template project ```bash theme={null} stoffel init my-python-project --template python cd my-python-project ``` Inspect the generated files: ```bash theme={null} find . -maxdepth 3 -type f | sort cat README.md ``` The template includes a nested Stoffel program that you can check and run through the CLI: ```bash theme={null} stoffel check stoffel stoffel run stoffel --input a=40 --input b=2 ``` If the generated program has different inputs, use the generated `README.md` and `stoffel/src/program.stfl` as the source of truth. ## Use Rust SDK examples for executable flows The Stoffel repository includes verified Rust SDK examples: ```bash theme={null} cd /path/to/stoffel cargo run -p stoffel-rust-sdk --example quickstart cargo run -p stoffel-rust-sdk --example local_mpc_named_inputs cargo run -p stoffel-rust-sdk --example local_mpc_client_input ``` ## Use the CLI directly from Python projects Until a Python package is finalized, a Python application can treat Stoffel as a build/run tool invoked around a nested Stoffel project: ```bash theme={null} stoffel check stoffel stoffel build stoffel stoffel run stoffel --input a=40 --input b=2 ``` For `ClientStore` examples, use client-slot inputs: ```bash theme={null} stoffel run stoffel --client-input 0=42 --parties 5 --threshold 1 ``` ## Python package API status Some design sketches use names such as `StoffelProgram`, `StoffelClient`, or `execute_with_inputs`. Do not present those as 0.1.0 Python package APIs; use the Python template for scaffolding and the Rust SDK for executable application flows. ## Next steps * [Rust SDK Examples](../rust-sdk/examples) * [CLI Overview](../cli/overview) * [StoffelLang Overview](../stoffel-lang/overview) # Installation Source: https://docs.stoffelmpc.com/python-sdk/installation Install the Stoffel CLI and create a Python-oriented project scaffold. **Python integration starts with the CLI template.** Install the `stoffel` CLI, then use its Python template to scaffold your project. Use the Rust SDK when your app needs execution APIs. ## Prerequisites Install the CLI first: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" stoffel --help ``` Verify the Python template is available: ```bash theme={null} stoffel init --help ``` The help output should list `python (py)` among the supported templates. ## Create a Python scaffold ```bash theme={null} stoffel init my-python-project --template python cd my-python-project ``` Inspect the generated README and manifests: ```bash theme={null} ls cat README.md find stoffel -maxdepth 3 -type f ``` Run the nested Stoffel program with the CLI: ```bash theme={null} stoffel check stoffel stoffel run stoffel --input a=40 --input b=2 ``` If your generated program expects different inputs, follow that template's `README.md` or `stoffel/src/program.stfl`. ## Python package status Stoffel 0.1.0 provides a CLI Python template rather than a Python SDK package. Use the CLI for scaffolding and the Rust SDK when your app needs execution APIs. ## Next steps * [Python SDK Overview](./overview) * [Rust SDK Overview](../rust-sdk/overview) * [Quick Start](../getting-started/quick-start) # Python SDK Overview Source: https://docs.stoffelmpc.com/python-sdk/overview Current status of Python integration in the Stoffel 0.1.0 release. **Status: Python template in 0.1.0** Stoffel 0.1.0 includes `stoffel init --template python` for Python-oriented project structure. The Rust SDK is the executable application API for this release; use the Python template to organize a project around a Stoffel program while Python package APIs are being designed and validated. ## What exists today The CLI can scaffold a Python-oriented project: ```bash theme={null} stoffel init my-python-project --template python cd my-python-project ``` The template includes a Python package scaffold plus a nested Stoffel program directory. Use the generated `README.md`, `pyproject.toml`, and `stoffel/Stoffel.toml` as the source of truth for the exact files created by your installed CLI. You can still use the Stoffel CLI against the nested Stoffel program: ```bash theme={null} stoffel check stoffel stoffel run stoffel --input a=40 --input b=2 ``` ## Recommended 0.1.0 path For executable applications today, use one of these paths: 1. Rust SDK: [Rust SDK Overview](../rust-sdk/overview) 2. CLI project flow: [Quick Start](../getting-started/quick-start) 3. Python scaffold for integration planning while the Python package matures The Rust SDK currently provides compile/load/run builders, clear execution, local MPC execution, network configuration types, client/server types, observability helpers, and coordinator handles. ## Python package direction Python package APIs should wrap the same core concepts exposed by the CLI and Rust SDK: * program compilation/loading * named inputs and client-slot inputs * local execution and local MPC development * network client configuration * explicit public/secret input boundaries For 0.1.0, Python package snippets are design-oriented; use the Python template for project structure and the Rust SDK for executable flows. ## Next steps * [Python Template Installation](./installation) * [Rust SDK Overview](../rust-sdk/overview) * [CLI Overview](../cli/overview) # API Reference Source: https://docs.stoffelmpc.com/rust-sdk/api Current Rust SDK API surface for compiling, loading, executing, and configuring Stoffel programs. Import the SDK through the `stoffel` crate name: ```rust theme={null} use stoffel::prelude::*; ``` The Cargo package is `stoffel-rust-sdk`, but the library crate is `stoffel`. ## Program entry points ```rust theme={null} // Compile source text. Useful for small tests. Stoffel::compile(source)? // Compile a source file. Useful for tooling. Stoffel::compile_file("src/main.stfl")? // Load compiled bytecode bytes. Stoffel::load(&bytecode)? // Load compiled bytecode from disk. Preferred for app wrappers after `stoffel build`. Stoffel::load_file("target/debug/hello-mpc.stflb")? // Load an executable runtime directly from bytes, a PathBuf, or a Program. Stoffel::load_program(std::path::PathBuf::from("target/debug/hello-mpc.stflb"))? ``` For app integrations, prefer `stoffel build` followed by `Stoffel::load_file(...)` so Rust code executes the same `.stflb` artifact that the CLI checked and built. ## Builder configuration ```rust theme={null} Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .instance_id(1) .backend(MpcBackend::HoneyBadger) .build()?; ``` Common settings: | Method | Purpose | | -------------------------------------------------------- | ------------------------------------------------------------------------------- | | `.parties(n)` | Number of MPC parties/nodes for local or network execution. | | `.threshold(t)` | Fault/security threshold. Current local/network config validates `n >= 4t + 1`. | | `.instance_id(id)` | Computation instance identifier. | | `.backend(MpcBackend::HoneyBadger)` | Select HoneyBadgerMPC, the default field-arithmetic backend. | | `.backend(MpcBackend::Avss { curve })` | Select AVSS for group/scalar workflows and threshold-cryptography examples. | | `.curve(Curve::Secp256k1)` | Shortcut for selecting AVSS with a specific curve. | | `.compiler_options(options)` | Adjust compilation when compiling source. | | `.network_config(config)` / `.network_config_file(path)` | Configure network client execution. | Supported AVSS curves include `Curve::Bls12_381`, `Curve::Bn254`, `Curve::Curve25519`, `Curve::Ed25519`, `Curve::Secp256k1`, and `Curve::Secp256r1`. See [MPC Protocols](../mpc-protocols/overview) for backend selection guidance. ## Inputs Named function inputs: ```rust theme={null} .with_inputs(&[("a", 40_i64), ("b", 2_i64)]) .with_input("a", 40_i64) .with_input_file("inputs.json")? ``` ClientStore inputs: ```rust theme={null} .with_client_input(0, &[42_i64]) .with_client_input_file("client-inputs.json")? ``` `with_client_input(0, &[42_i64])` supplies values read by StoffelLang calls such as `ClientStore.take_share(0, 0)`. ## Execution Clear execution is synchronous and skips MPC. Use it for fast logic checks: ```rust theme={null} let result = Stoffel::compile("def main(a: int64, b: int64) -> int64:\n return a + b")? .with_inputs(&[("a", 40_i64), ("b", 2_i64)]) .execute_clear()?; ``` Local MPC execution is async and runs a local MPC test network on your machine: ```rust theme={null} # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; # Ok(()) # } ``` Named-function local MPC execution: ```rust theme={null} # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_inputs(&[("a", 40_i64), ("b", 2_i64)]) .execute_local_function("add_private_values") .await?; # Ok(()) # } ``` ## Runtime and bytecode helpers ```rust theme={null} let runtime = Stoffel::compile_file("src/main.stfl")? .parties(5) .threshold(1) .build()?; runtime.save_bytecode("target/debug/hello-mpc.stflb")?; let bytecode = runtime.to_bytecode()?; let summary = runtime.bytecode_summary()?; let program = runtime.program(); ``` ## Program metadata A loaded runtime exposes program metadata used by generated bindings and debugging tools: ```rust theme={null} let runtime = Stoffel::load_file("target/debug/hello-mpc.stflb")?.build()?; let program = runtime.program(); println!("functions={}", program.function_count()); if let Some(client) = program.client(0) { println!("client slot {} inputs={}", client.client_slot(), client.input_count()); } ``` ## Network configuration The SDK can generate network configuration files for deployments where MPC nodes run separately from input/output clients: ```rust theme={null} let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::HoneyBadger) .preprocessing(1000, 500) .build()?; let paths = deployment.save_toml_files("./network-configs")?; let config = NetworkConfig::from_toml_file(&paths[0])?; let server = StoffelServer::builder(0).network_deployment(&deployment).build()?; let client = StoffelClient::builder().network_config(&config).build()?; ``` For most application developers, start with CLI builds and `.execute_local().await?` before using network configuration directly. Use `MpcBackend::Avss { curve: Curve::Bls12_381 }` for AVSS network/client experiments that use the current SDK off-chain client I/O path. Other AVSS curves are selectable for bytecode/runtime paths and curve-aware StoffelLang examples; validate the client/network path before relying on a specific curve. ## Errors and observability ```rust theme={null} let tracing = TracingConfig::builder() .service_name("stoffel-app") .ansi(false) .build(); let error = Error::Preprocessing("not enough triples".to_owned()); println!("category={} recoverable={}", error.category(), error.is_recoverable()); println!("tracing={:?}", tracing.summary()); ``` ## See also * [Rust SDK Overview](./overview) * [Examples](./examples) * [CLI Overview](../cli/overview) * [MPC Protocols](../mpc-protocols/overview) # Rust SDK App Integration Source: https://docs.stoffelmpc.com/rust-sdk/app-integration Embed a Stoffel program inside a Rust service with explicit client inputs, local MPC execution, and typed domain outputs. Use the Rust SDK when Stoffel is part of an application rather than a standalone CLI run. The app keeps ordinary product logic in Rust and moves the privacy-sensitive computation into StoffelLang. If you want a complete minimal project before reading the pattern, run the [Rust SDK quickstart](../tutorials/rust-sdk-quickstart). It is the smallest app-shaped Rust example before the larger tutorial apps. The shape to aim for is: ```text theme={null} Rust domain request → client-owned private payloads → Stoffel ClientStore inputs → local MPC development run → opened result → Rust domain response ``` ## Recommended project shape A small app-shaped project usually has: ```text theme={null} src/ ├── app.rs # service boundary that calls Stoffel ├── client.rs # client-owned private payloads ├── domain.rs # ordinary product types ├── lib.rs # module exports and generated bindings when used ├── main.rs # sample app entrypoint ├── main.stfl # private computation └── bin/ └── *_client.rs # optional client payload demo ``` The tutorial repository uses this structure in the [Rust SDK quickstart](../tutorials/rust-sdk-quickstart), [Private Lottery](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/01-private-lottery), [Private Matchmaking](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/02-private-matchmaking), and [N-party Battleship](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/03-n-party-battleship) tutorials. ## Service boundary responsibilities `src/app.rs` should own the SDK handoff: 1. Receive normal Rust domain input. 2. Validate public application shape, such as participant count or slot layout. 3. Keep each client payload attached to a client slot. 4. Compile `src/main.stfl` or load built `.stflb` bytecode. 5. Configure local MPC settings such as parties, threshold, backend, and timeout. 6. Attach private inputs with `.with_client_input(slot, values)`. 7. Build the runtime and validate client inputs. 8. Run local MPC for the tutorial/dev smoke test. 9. Decode the opened result into a Rust domain type. 10. Keep ordinary policy decisions, persistence, display names, and routing in Rust when they do not need private computation. ## Minimal pattern ```rust theme={null} use std::{path::PathBuf, time::Duration}; use stoffel::prelude::*; pub struct PrivateFeatureService { program_path: PathBuf, parties: usize, threshold: usize, timeout: Duration, } impl PrivateFeatureService { pub async fn run_round(&self, client_inputs: Vec<(u64, Vec)>) -> stoffel::Result { let mut builder = Stoffel::compile_file(&self.program_path)? .parties(self.parties) .threshold(self.threshold) .honeybadger(); for (slot, values) in client_inputs { builder = builder.with_client_input(slot, &values); } let runtime = builder.build()?; runtime.validate_client_inputs()?; let values = runtime .local_network() .entry("main") .timeout(self.timeout) .run() .await?; values .first() .and_then(Value::as_i64) .ok_or_else(|| Error::Computation(format!("expected int64 output, got {values:?}"))) } } ``` This pattern is for local development. It runs several MPC nodes/processes locally on your machine. A deployed network uses the same program boundary but different server/client configuration. ## Client payloads Give each logical participant a Rust type that owns its private payload: ```rust theme={null} #[derive(Debug, Clone)] pub struct VoterClient { pub voter_id: String, pub client_slot: u64, pub approves: bool, } impl VoterClient { pub fn input_values(&self) -> [i64; 1] { [i64::from(self.approves)] } } ``` Then attach the payload at the service boundary: ```rust theme={null} for voter in voters { builder = builder.with_client_input(voter.client_slot, &voter.input_values()); } ``` Prefer one client slot per logical participant when the product has distinct participants. That keeps ownership clear and makes the privacy boundary easier to explain. ## StoffelLang boundary The Stoffel program should load only the private values it needs: ```stoffel theme={null} def main() -> int64: var yes_votes: secret int64 = Share.from_clear_int(0, 64) var client_count = ClientStore.get_number_input_clients() for client_id in 0..client_count: var vote: secret int64 = ClientStore.take_share(client_id, 0) yes_votes = yes_votes + vote return yes_votes.reveal() ``` The host app can apply ordinary public policy afterward, such as `yes_votes >= approvals_required`. ## Compile source or load bytecode For development tutorials, compiling source keeps the example easy to read: ```rust theme={null} let runtime = Stoffel::compile_file("src/main.stfl")? .parties(5) .threshold(1) .build()?; ``` For application builds, use the CLI to build bytecode and load the artifact: ```bash theme={null} stoffel build ``` ```rust theme={null} let runtime = Stoffel::load_file("target/debug/my-app.stflb")? .parties(5) .threshold(1) .build()?; ``` Generated bindings can carry program metadata into the Rust app. Regenerate or update them when the `ClientStore` input/output shape changes. ## Verification loop Use both Rust and Stoffel checks: ```bash theme={null} cargo test cargo run stoffel status --verbose stoffel check stoffel build ``` Then run the tutorial's exact local MPC command. If the tutorial uses newer StoffelLang helpers than your installed CLI supports, use the CLI version named by the tutorial repository until those APIs are in the default installer. ## Next steps * [Rust SDK quickstart](../tutorials/rust-sdk-quickstart) * [Private Matchmaking tutorial](../tutorials/private-matchmaking) * [Design the privacy boundary](../tutorials/privacy-boundary) * [Rust SDK examples](./examples) # Examples Source: https://docs.stoffelmpc.com/rust-sdk/examples Current Rust SDK examples for clear execution, local MPC, bytecode, ClientStore inputs, network config, and observability. These examples match the Stoffel 0.1.0 SDK in the `stoffel` repository. Run examples from the repository root: ```bash theme={null} cd /path/to/stoffel cargo run -p stoffel-rust-sdk --example quickstart cargo run -p stoffel-rust-sdk --example local_mpc_named_inputs cargo run -p stoffel-rust-sdk --example local_mpc_client_input ``` ## Clear local execution Use clear execution for fast logic checks before moving to local MPC runs. ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { 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()?; println!("Result: {}", result[0]); Ok(()) } ``` Expected output: ```text theme={null} Result: 100 ``` Repository file: `crates/stoffel-rust-sdk/examples/quickstart.rs`. ## Direct `secret int64` arguments The repository includes `crates/stoffel-rust-sdk/examples/quickstart_mpc.rs`, which demonstrates direct `secret int64` function arguments: ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let result = Stoffel::compile( "def main(a: secret int64, b: secret int64) -> secret int64:\n return a + b", )? .parties(5) .threshold(1) .with_inputs(&[("a", 42_i64), ("b", 58_i64)]) .execute_local() .await; match result { Ok(values) => println!("Private result: {}", values[0]), Err(stoffel::Error::Unsupported(message)) => println!("Local MPC unavailable: {message}"), Err(error) => return Err(error), } Ok(()) } ``` Use the `Share` and `ClientStore` examples below as local MPC smoke tests. They exercise explicit private inputs and deliberate reveal points. ## Named `Share` inputs Use this when the source function accepts share values directly and opens only the intended output. ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let source = r#" def add_private_values(a: Share, b: Share) -> int64: var sum = Share.add(a, b) return sum.open() "#; match Stoffel::compile(source)? .parties(5) .threshold(1) .with_inputs(&[("a", 42_i64), ("b", 58_i64)]) .execute_local_function("add_private_values") .await { Ok(result) => println!("Local MPC named-input result: {}", result[0]), Err(stoffel::Error::Unsupported(message)) => println!("Local MPC unavailable: {message}"), Err(error) => return Err(error), } Ok(()) } ``` Repository file: `crates/stoffel-rust-sdk/examples/local_mpc_named_inputs.rs`. ## ClientStore local inputs Use `ClientStore.take_share` when modeling local client slots. ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let source = r#" def main() -> int64: var share = ClientStore.take_share(0, 0) var opened: int64 = share.open() return opened + 5 "#; let runtime = Stoffel::compile(source)?.parties(5).threshold(1).build()?; let client = runtime.program().client(0).expect("client slot 0"); println!( "Program expects {} input(s) from client slot {}", client.input_count(), client.client_slot() ); match Stoffel::load(&runtime.program().to_bytecode()?)? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await { Ok(result) => println!("Local MPC result: {}", result[0]), Err(stoffel::Error::Unsupported(message)) => println!("Local MPC unavailable: {message}"), Err(error) => return Err(error), } Ok(()) } ``` Repository file: `crates/stoffel-rust-sdk/examples/local_mpc_client_input.rs`. ## Bytecode round trip Compile source, save bytecode, load it back, and execute with inputs. ```rust theme={null} use std::time::{SystemTime, UNIX_EPOCH}; use stoffel::prelude::*; fn main() -> stoffel::Result<()> { let source = "def main(a: int64, b: int64) -> int64:\n return a * b"; let runtime = Stoffel::compile(source)?.build()?; let bytecode_path = std::env::temp_dir().join(format!( "stoffel-sdk-bytecode-{}-{}.stflb", std::process::id(), SystemTime::now() .duration_since(UNIX_EPOCH) .map(|duration| duration.as_nanos()) .unwrap_or_default() )); runtime.save_bytecode(&bytecode_path)?; let summary = runtime.bytecode_summary()?; let result = Stoffel::load_file(&bytecode_path)? .with_inputs(&[("a", 6_i64), ("b", 7_i64)]) .execute_clear()?; let _ = std::fs::remove_file(&bytecode_path); println!( "Bytecode round-trip result: {} ({} bytes, {} function(s))", result[0], summary.byte_len, summary.program.function_count ); Ok(()) } ``` Repository file: `crates/stoffel-rust-sdk/examples/bytecode_roundtrip.rs`. ## Network configuration Generate TOML configs for a local network deployment and build server/client handles from those configs. ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::HoneyBadger) .preprocessing(1000, 500) .build()?; let config_dir = std::env::temp_dir().join("stoffel-sdk-network-configs"); let paths = deployment.save_toml_files(&config_dir)?; let reparsed = NetworkConfig::from_toml_file(&paths[0])?; let _ = std::fs::remove_dir_all(&config_dir); let server = StoffelServer::builder(0) .network_deployment(&deployment) .build()?; let client = StoffelClient::builder().network_config(&reparsed).build()?; println!( "Configured party {} on {} with {} peer(s); client sees {} server(s)", server.party_id(), server.bind_addr(), server.peers().len(), client.servers().len() ); Ok(()) } ``` Repository file: `crates/stoffel-rust-sdk/examples/network_config.rs`. ## Observability and health ```rust theme={null} use std::time::Duration; use stoffel::prelude::*; use tracing::Level; fn main() -> stoffel::Result<()> { let tracing = TracingConfig::builder() .service_name("stoffel-observability-example") .max_level(Level::INFO) .ansi(false) .build(); let server = StoffelServer::builder(0) .bind("127.0.0.1:19400") .with_preprocessing(100, 50) .build()?; server.metrics().record_connected_peers(4); server.metrics().record_connected_clients(1); server.metrics().record_preprocessing_remaining(80, 40); server .metrics() .record_computation_latency(Duration::from_millis(25)); server.metrics().increment_computations_completed(); let snapshot = server.metrics().snapshot(); println!( "health={} peers={} completed={}", server.health(), snapshot.connected_peers, snapshot.computations_completed ); let error = Error::Preprocessing("not enough triples".to_owned()); println!( "error_category={} recoverable={} hint={:?}", error.category(), error.is_recoverable(), error.recovery_hint() ); println!("Tracing summary: {:?}", tracing.summary()); Ok(()) } ``` Repository file: `crates/stoffel-rust-sdk/examples/observability.rs`. ## Choosing an input style | Program shape | SDK input API | CLI equivalent | | -------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------- | | `def main(a: int64, b: int64)` | `.with_inputs(&[("a", 1_i64), ("b", 2_i64)])` | `--input a=1 --input b=2` | | `def main(a: secret int64, b: secret int64)` | `.with_inputs(&[("a", 1_i64), ("b", 2_i64)])` plus `.execute_local().await` | `--input a=1 --input b=2` with local execution | | `ClientStore.take_share(0, 0)` | `.with_client_input(0, &[1_i64])` | `--client-input 0=1` | ## Next steps * [Rust SDK Installation](./installation) * [Quick Start](../getting-started/quick-start) * [CLI Overview](../cli/overview) # Installation Source: https://docs.stoffelmpc.com/rust-sdk/installation Install the Stoffel CLI and add the Rust SDK to your app. The Rust SDK is the application API for building Stoffel into your product. Its Cargo package is `stoffel-rust-sdk`; the Rust crate you import is `stoffel`. **Stoffel 0.1.0 Rust SDK** Install the CLI for project commands. Add the Rust SDK when your app needs to compile, load, or run Stoffel programs directly. ## Prerequisites * Rust and Cargo, latest stable recommended * The `stoffel` CLI * `stoffel-rust-sdk` from crates.io * A local checkout of [`Stoffel-Labs/stoffel`](https://github.com/Stoffel-Labs/stoffel) only when you need source examples or local crate changes Install the CLI: ```bash theme={null} curl -fsSL https://get.stoffelmpc.com | sh export PATH="$HOME/.local/bin:$PATH" stoffel --help ``` ## Add the SDK from crates.io ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", version = "0.1.0" } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } ``` ## Optional: use a local checkout Use a path dependency when your app needs local SDK source changes: ```toml theme={null} [dependencies] stoffel = { package = "stoffel-rust-sdk", path = "../stoffel/crates/stoffel-rust-sdk" } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } ``` Adjust the relative path to match your project layout. ## Verify clear execution Create `src/main.rs`: ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { 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()?; println!("Result: {}", result[0]); Ok(()) } ``` Run it: ```bash theme={null} cargo run ``` Expected output: ```text theme={null} Result: 100 ``` ## Verify local MPC execution For an app-shaped local MPC check, build your Stoffel project first and load the bytecode from Rust: ```bash theme={null} stoffel build ``` ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; println!("Local MPC result: {}", result[0]); Ok(()) } ``` `.execute_local().await?` runs local MPC testing by spawning several MPC nodes/processes on your machine. ## Current SDK surface The SDK currently includes: * `Stoffel::compile(source)` and `Stoffel::compile_file(path)` for source compilation. * `Stoffel::load(bytecode)` and `Stoffel::load_file(path)` for bytecode loading. * `with_inputs(&[(name, value)])` for named function inputs. * `with_client_input(slot, &[values])` for ClientStore-style local MPC programs. * `execute_clear()` for fast local logic checks. * `execute_local().await` and `execute_local_function(name).await` for local MPC execution. * `parties(n)` and `threshold(t)` MPC configuration. * bytecode save/load and summary helpers. * network deployment/config builders for multi-node experiments. * observability and error-category helpers. * on-chain coordinator handles for integration experiments. ## Examples in the repository The SDK examples live at: ```text theme={null} crates/stoffel-rust-sdk/examples/ ``` Useful starting points: | Example | What it shows | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `quickstart.rs` | Clear local execution with named inputs. | | `local_mpc_named_inputs.rs` | Recommended local MPC smoke test using `Share` values with named inputs. | | `local_mpc_client_input.rs` | Recommended ClientStore smoke test using local client slots. | | `quickstart_mpc.rs` | Direct `secret int64` argument example; useful for reading, but the `Share` and `ClientStore` examples are the more reliable current smoke tests. | | `bytecode_roundtrip.rs` | Save bytecode, load it back, and execute. | | `network_config.rs` | Generate and parse network deployment TOML. | | `observability.rs` | Tracing, metrics, server health, and error categories. | | `onchain.rs` | On-chain coordinator handle setup. | Run an example from the repository: ```bash theme={null} cd /path/to/stoffel cargo run -p stoffel-rust-sdk --example quickstart cargo run -p stoffel-rust-sdk --example local_mpc_named_inputs cargo run -p stoffel-rust-sdk --example local_mpc_client_input ``` ## Troubleshooting ### `use stoffel::prelude::*` cannot resolve Confirm that your dependency renames the crates.io package to the library crate name: ```toml theme={null} stoffel = { package = "stoffel-rust-sdk", version = "0.1.0" } ``` ### Cargo builds take a while The SDK pulls MPC, networking, and coordinator crates through Cargo. The first build can take several minutes. ## Next steps * [Rust SDK Examples](./examples) * [CLI Overview](../cli/overview) * [Quick Start](../getting-started/quick-start) # Rust SDK Overview Source: https://docs.stoffelmpc.com/rust-sdk/overview Primary 0.1.0 application API for compiling, loading, and running Stoffel programs from Rust. The Rust SDK is the primary application integration surface for Stoffel 0.1.0. It lives in the `stoffel` repository at `crates/stoffel-rust-sdk`. * Cargo package name: `stoffel-rust-sdk` * Rust library crate name: `stoffel` * Common import: `use stoffel::prelude::*;` **Stoffel 0.1.0 Rust SDK** The Rust SDK is the primary application API for the 0.1.0 release. Validate security and deployment assumptions for your application before going live. ## What the SDK does The SDK gives Rust applications a single API for: * compiling Stoffel Lang source; * loading compiled `.stflb` bytecode; * running clear local checks; * running local MPC testing; * passing named function inputs; * passing ClientStore-style local client inputs; * saving, loading, and summarizing bytecode; * generating network deployment configs; * building server/client handles for networked experiments; * collecting basic observability and error-category information. ## Mental model Rust SDK runtime paths from Rust application code through compile/load APIs into clear local execution, local MPC execution, and network handles. Campaign-style Stoffel workflow diagram showing how source programs, the build contract, coordinator-managed runtime, and application clients connect. Use `Stoffel::compile(...)` when the Rust app owns source compilation. Use `Stoffel::load(...)` or `Stoffel::load_file(...)` when the app should load a `.stflb` artifact built by the CLI. For app integrations, a common flow is to build bytecode with the CLI and load that artifact from Rust: ```bash theme={null} stoffel build ``` ```rust theme={null} let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; ``` ## Clear local execution Use clear execution to validate ordinary business logic quickly: ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { 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()?; println!("Result: {}", result[0]); Ok(()) } ``` ## Local MPC execution Use local execution to test secret-value and MPC boundaries. The most reliable 0.1.0 smoke path uses `Share` inputs and opens only the intended output: ```rust theme={null} use stoffel::prelude::*; #[tokio::main] async fn main() -> stoffel::Result<()> { let source = r#" def add_private_values(a: Share, b: Share) -> int64: var sum = Share.add(a, b) return sum.open() "#; match Stoffel::compile(source)? .parties(5) .threshold(1) .with_inputs(&[("a", 42_i64), ("b", 58_i64)]) .execute_local_function("add_private_values") .await { Ok(result) => println!("Local MPC named-input result: {}", result[0]), Err(stoffel::Error::Unsupported(message)) => println!("Local MPC unavailable: {message}"), Err(error) => return Err(error), } Ok(()) } ``` The local MPC path is exposed through the SDK and the `stoffel` CLI workflows. It runs several MPC nodes/processes locally on the developer machine. ## Core APIs ### Compile and load ```rust theme={null} Stoffel::compile(source)? Stoffel::compile_file("src/main.stfl")? Stoffel::load(bytecode)? Stoffel::load_file("target/debug/program.stflb")? ``` ### Inputs ```rust theme={null} // Named function inputs. .with_inputs(&[("a", 42_i64), ("b", 58_i64)]) // ClientStore slot inputs. .with_client_input(0, &[42_i64]) ``` ### Execution ```rust theme={null} .execute_clear()? // synchronous clear execution .execute_local().await // async local MPC execution .execute_local_function("name") // async local MPC named-function execution ``` ### MPC configuration ```rust theme={null} .parties(5) .threshold(1) .backend(MpcBackend::HoneyBadger) .backend(MpcBackend::Avss { curve: Curve::Secp256k1 }) ``` Current local and network MPC config validates `parties >= 4 * threshold + 1`. The common developer default is five parties and threshold one. Use `MpcBackend::HoneyBadger` when secret application values are field-compatible shares and the outside system consumes an opened result or client-output shares. Use `MpcBackend::Avss { curve }` when the output boundary needs public commitments, curve-encoded values, opened scalar responses, or signature-related artifacts. See [MPC Backends](../mpc-protocols/overview) for backend details. ### Bytecode ```rust theme={null} let runtime = Stoffel::compile(source)?.build()?; runtime.save_bytecode("program.stflb")?; let summary = runtime.bytecode_summary()?; let bytecode = runtime.program().to_bytecode()?; ``` ## Network and infrastructure surfaces The SDK includes builders for network deployments and server/client handles. These APIs are useful for integration experiments and infrastructure planning: ```rust theme={null} let deployment = NetworkDeployment::builder([ "127.0.0.1:19200", "127.0.0.1:19201", "127.0.0.1:19202", "127.0.0.1:19203", "127.0.0.1:19204", ]) .expected_clients(1) .threshold(1) .backend(MpcBackend::HoneyBadger) .preprocessing(1000, 500) .build()?; let paths = deployment.save_toml_files("./network-configs")?; let config = NetworkConfig::from_toml_file(&paths[0])?; let server = StoffelServer::builder(0).network_deployment(&deployment).build()?; let client = StoffelClient::builder().network_config(&config).build()?; ``` For most app developers, start with clear execution and local MPC before using network configuration directly. The same builders accept `MpcBackend::Avss { curve: Curve::Bls12_381 }` for AVSS network/client experiments supported by the current off-chain client I/O path. Use curve-specific AVSS examples when the external protocol fixes the curve or verifier-facing artifact. ## Observability and errors The SDK exposes tracing config, server metrics, health status, and error categories: ```rust theme={null} let tracing = TracingConfig::builder() .service_name("stoffel-app") .ansi(false) .build(); let error = Error::Preprocessing("not enough triples".to_owned()); println!("category={} recoverable={}", error.category(), error.is_recoverable()); println!("tracing={:?}", tracing.summary()); ``` ## Where to find examples Examples are checked into the repository: ```text theme={null} crates/stoffel-rust-sdk/examples/ ``` Start with: * `quickstart.rs` * `local_mpc_named_inputs.rs` * `local_mpc_client_input.rs` * `quickstart_mpc.rs` for the direct `secret int64` argument shape * `bytecode_roundtrip.rs` * `network_config.rs` * `observability.rs` For a complete app-shaped project, use the [Rust SDK quickstart](../tutorials/rust-sdk-quickstart). It adds domain types, client-owned private payloads, a repository boundary, tests, and a runnable service around the same SDK pattern. ## Next steps * [Install the Rust SDK](./installation) * [Run the Rust SDK quickstart](../tutorials/rust-sdk-quickstart) * [Run SDK examples](./examples) * [Use the CLI](../cli/overview) # Access Control Source: https://docs.stoffelmpc.com/solidity-sdk/access-control Role-based access control for MPC parties with PARTY_ROLE and DESIGNATED_PARTY_ROLE permissions. The `StoffelAccessControl` contract provides role-based access control for MPC parties, ensuring only authorized addresses can participate in the computation. ## Overview ```solidity theme={null} abstract contract StoffelAccessControl is AccessControl { bytes32 public constant PARTY_ROLE = keccak256("PARTY_ROLE"); bytes32 public constant DESIGNATED_PARTY_ROLE = keccak256("DESIGNATED_PARTY_ROLE"); } ``` **Inheritance:** OpenZeppelin's `AccessControl` ## Roles ### PARTY\_ROLE Assigned to MPC compute nodes (servers). Parties can: * Participate in MPC protocol execution * Submit computation results * Access party-restricted functions ### DESIGNATED\_PARTY\_ROLE Elevated role for orchestration. The designated party can: * Trigger round transitions * Initialize input mask buffers * Coordinate preprocessing and output phases * All permissions of PARTY\_ROLE ## Storage ```solidity theme={null} // Number of parties (n) uint256 public nParties; // Fault tolerance threshold (t) uint256 public threshold; ``` ## Constructor ```solidity theme={null} constructor( uint256 n, uint256 t, address designatedParty, address[] memory initialMPCNodes ) { require(n >= 3 * t + 1, "Invalid n/t configuration"); require(initialMPCNodes.length <= n, "Too many initial nodes"); nParties = n; threshold = t; // Grant designated party role _grantRole(DESIGNATED_PARTY_ROLE, designatedParty); _grantRole(PARTY_ROLE, designatedParty); // Grant party role to all MPC nodes for (uint i = 0; i < initialMPCNodes.length; i++) { _grantRole(PARTY_ROLE, initialMPCNodes[i]); } } ``` ## Modifiers ### onlyParty Restricts function to addresses with PARTY\_ROLE. ```solidity theme={null} modifier onlyParty() { require(hasRole(PARTY_ROLE, msg.sender), "Caller is not a party"); _; } // Usage function submitShare(bytes calldata share) external onlyParty { // Only MPC nodes can submit shares } ``` ### onlyDesignatedParty Restricts function to the designated party. ```solidity theme={null} modifier onlyDesignatedParty() { require(hasRole(DESIGNATED_PARTY_ROLE, msg.sender), "Caller is not designated party"); _; } // Usage function startPreprocessing() external onlyDesignatedParty { // Only designated party can start preprocessing } ``` ## Party Management ### Adding Parties ```solidity theme={null} function addParty(address party) external onlyDesignatedParty { require(!hasRole(PARTY_ROLE, party), "Already a party"); require(getPartyCount() < nParties, "Max parties reached"); _grantRole(PARTY_ROLE, party); } ``` ### Removing Parties ```solidity theme={null} function removeParty(address party) external onlyDesignatedParty { require(hasRole(PARTY_ROLE, party), "Not a party"); require(getPartyCount() > threshold + 1, "Cannot go below threshold"); _revokeRole(PARTY_ROLE, party); } ``` ### Querying Party Status ```solidity theme={null} function isParty(address account) public view returns (bool) { return hasRole(PARTY_ROLE, account); } function isDesignatedParty(address account) public view returns (bool) { return hasRole(DESIGNATED_PARTY_ROLE, account); } function getPartyCount() public view returns (uint256) { return getRoleMemberCount(PARTY_ROLE); } ``` ## Constraints ### n >= 3t + 1 The HoneyBadger protocol requires `n >= 3t + 1`: ```solidity theme={null} function validateConfiguration(uint256 n, uint256 t) internal pure { require(n >= 3 * t + 1, "n must be >= 3t + 1 for Byzantine fault tolerance"); } ``` ### Minimum Party Threshold Parties cannot be removed if it would violate the threshold: ```solidity theme={null} function canRemoveParty() public view returns (bool) { return getPartyCount() > threshold + 1; } ``` ## Designated Party Transfer ```solidity theme={null} function transferDesignatedParty(address newDesignatedParty) external onlyDesignatedParty { require(newDesignatedParty != address(0), "Invalid address"); _revokeRole(DESIGNATED_PARTY_ROLE, msg.sender); _grantRole(DESIGNATED_PARTY_ROLE, newDesignatedParty); // New designated party also gets PARTY_ROLE if (!hasRole(PARTY_ROLE, newDesignatedParty)) { _grantRole(PARTY_ROLE, newDesignatedParty); } } ``` ## Events ```solidity theme={null} // Inherited from OpenZeppelin AccessControl event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); ``` ## Example Usage ### Deploy with Initial Parties ```solidity theme={null} address[] memory mpcNodes = new address[](5); mpcNodes[0] = 0x1111...; mpcNodes[1] = 0x2222...; mpcNodes[2] = 0x3333...; mpcNodes[3] = 0x4444...; mpcNodes[4] = 0x5555...; MyCoordinator coordinator = new MyCoordinator( programHash, 5, // n = 5 parties 1, // t = 1 (tolerates 1 faulty) designatedPartyAddr, mpcNodes ); ``` ### Check Permissions ```solidity theme={null} // Check if address is a party bool canCompute = coordinator.isParty(someAddress); // Check if designated party bool canOrchestrate = coordinator.isDesignatedParty(someAddress); // Get current party count uint256 activeParties = coordinator.getPartyCount(); ``` ### Dynamic Party Management ```solidity theme={null} // Add a new MPC node (designated party only) coordinator.addParty(newNodeAddress); // Remove an MPC node (must maintain threshold) if (coordinator.canRemoveParty()) { coordinator.removeParty(oldNodeAddress); } // Transfer designated party role coordinator.transferDesignatedParty(newDesignatedPartyAddress); ``` ## Security Considerations 1. **Threshold maintenance**: Never allow party count to drop below `t + 1` 2. **Designated party security**: Protect the designated party key 3. **Role separation**: Use separate addresses for different roles when possible 4. **Multi-sig**: Consider using a multi-sig for designated party role in production ## Next Steps * [StoffelCoordinator](./coordinator): State machine details * [Input Manager](./input-manager): Client input handling * [Overview](./overview): Architecture overview # StoffelCoordinator Source: https://docs.stoffelmpc.com/solidity-sdk/coordinator Abstract contract implementing a 7-phase state machine for orchestrating MPC computations on-chain. The `StoffelCoordinator` is an abstract contract that implements a 7-phase state machine for orchestrating MPC computations on-chain. ## Overview ```solidity theme={null} abstract contract StoffelCoordinator is StoffelAccessControl, StoffelInputManager, Ownable { // State machine for MPC coordination } ``` **Inheritance:** * `StoffelAccessControl`: Role-based permissions * `StoffelInputManager`: Client input handling * `Ownable`: OpenZeppelin ownership ## State Machine ### Round Enum ```solidity theme={null} enum Round { PreprocessingRound, // 0 ClientInputMaskReservationRound, // 1 CollectingClientInputRound, // 2 ClientInputsCollectionEndRound, // 3 MPCTaskExecutionRound, // 4 MPCTaskExecutionEndRound, // 5 ClientOutputCollectionRound // 6 } ``` ### Round Descriptions | Round | Purpose | Who Acts | | ----------------------------------- | ----------------------------- | ---------------- | | **PreprocessingRound** | Initialize input mask buffer | Designated Party | | **ClientInputMaskReservationRound** | Clients reserve mask indices | Clients | | **CollectingClientInputRound** | Clients submit masked inputs | Clients | | **ClientInputsCollectionEndRound** | Finalize input collection | Coordinator | | **MPCTaskExecutionRound** | Off-chain MPC computation | MPC Nodes | | **MPCTaskExecutionEndRound** | Signal computation complete | MPC Nodes | | **ClientOutputCollectionRound** | Distribute results to clients | MPC Nodes | ### State Transitions ``` PreprocessingRound │ │ startPreprocessing() │ [Designated Party] ▼ ClientInputMaskReservationRound │ │ gatherInputs() or timeout │ [Designated Party] ▼ CollectingClientInputRound │ │ All inputs received or timeout │ ▼ ClientInputsCollectionEndRound │ │ initiateMPCComputation() │ [Designated Party] ▼ MPCTaskExecutionRound │ │ Off-chain computation │ [MPC Nodes] ▼ MPCTaskExecutionEndRound │ │ publishOutputs() │ [Designated Party] ▼ ClientOutputCollectionRound │ │ Clients collect outputs ▼ Done ``` ## Constructor ```solidity theme={null} constructor( bytes32 stoffelProgramHash, uint256 n, uint256 t, address designatedParty, address[] memory initialMPCNodes ) ``` **Parameters:** * `stoffelProgramHash`: Keccak256 hash of the compiled Stoffel program * `n`: Number of MPC parties * `t`: Fault tolerance threshold * `designatedParty`: Address with elevated privileges * `initialMPCNodes`: Array of addresses to grant PARTY\_ROLE **Validation:** * Checks `n >= 3t + 1` (HoneyBadger requirement) **Initialization:** * Stores `_stoffelProgramHash` * Records `creationTime` * Grants roles to parties and designated party * Emits `CoordinatorInitialized` event ## Round Modifiers ### atRound Enforces the current round matches the expected round. ```solidity theme={null} modifier atRound(Round _round) { require(currentRound == _round, "Invalid round"); _; } // Usage function startPreprocessing() external atRound(Round.PreprocessingRound) { // Only executes in PreprocessingRound } ``` ### nextRound Advances to the next round. ```solidity theme={null} modifier nextRound() { _; currentRound = Round(uint(currentRound) + 1); } // Usage function completePhase() external atRound(Round.CollectingClientInputRound) nextRound { // Advances to ClientInputsCollectionEndRound } ``` ### goToRound Jumps to a specific round (for skipping phases). ```solidity theme={null} modifier goToRound(Round _round) { _; currentRound = _round; } ``` ### timedRoundTransition Automatically advances if timeout elapsed since contract creation. ```solidity theme={null} modifier timedRoundTransition(Round transitionRound, uint whenToTransition) { if (currentRound == transitionRound && block.timestamp >= creationTime + whenToTransition) { currentRound = Round(uint(currentRound) + 1); } _; } ``` ### timedRoundTransitionGoto Timeout-based jump to specific round. ```solidity theme={null} modifier timedRoundTransitionGoto( Round transitionRound, Round gotoRound, uint whenToTransition ) { if (currentRound == transitionRound && block.timestamp >= creationTime + whenToTransition) { currentRound = gotoRound; } _; } ``` ## Virtual Functions Subclasses must implement these: ```solidity theme={null} // Start the preprocessing phase function startPreprocessing() external virtual; // Move from mask reservation to input collection function gatherInputs() external virtual; // Initiate the MPC computation function initiateMPCComputation() external virtual; // Publish computation outputs function publishOutputs() external virtual; ``` ### Example Implementation ```solidity theme={null} function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) nextRound { // Initialize input mask buffer initializeInputMaskBuffer(expectedClientCount); emit PreprocessingRoundExecuted(msg.sender, block.timestamp); } function gatherInputs() external override onlyDesignatedParty atRound(Round.ClientInputMaskReservationRound) nextRound { // Move to input collection phase } function initiateMPCComputation() external override onlyDesignatedParty atRound(Round.ClientInputsCollectionEndRound) nextRound { emit MPCTaskExecuted(_stoffelProgramHash, msg.sender, block.timestamp); } function publishOutputs() external override onlyDesignatedParty atRound(Round.MPCTaskExecutionEndRound) nextRound { // Make outputs available to clients } ``` ## Events ```solidity theme={null} // Emitted when coordinator is initialized event CoordinatorInitialized( address indexed coordinator, uint256 timeOfInitialization, address indexed designatedParty ); // Emitted after preprocessing completes event PreprocessingRoundExecuted( address indexed designatedParty, uint256 timeOfExecution ); // Emitted when mask reservation occurs event ClientInputMaskReservationEvent( address indexed executor, uint256 timeOfExecution ); // Emitted when MPC task is executed event MPCTaskExecuted( bytes32 indexed stoffelProgramHash, address indexed executor, uint256 timeOfExecution ); ``` ## Storage ```solidity theme={null} // Hash of the compiled Stoffel program bytes32 internal _stoffelProgramHash; // Timestamp of contract deployment uint256 public creationTime; // Current round in the state machine Round public currentRound; ``` ## Example: Complete Coordinator ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {StoffelCoordinator} from "stoffel-solidity-sdk/StoffelCoordinator.sol"; contract SecureAuction is StoffelCoordinator { uint256 public constant RESERVATION_TIMEOUT = 1 hours; uint256 public constant COLLECTION_TIMEOUT = 2 hours; mapping(address => uint256) public clientOutputs; constructor( bytes32 programHash, address[] memory mpcNodes ) StoffelCoordinator( programHash, mpcNodes.length, // n = number of nodes 1, // t = 1 msg.sender, // deployer is designated party mpcNodes ) {} function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) nextRound { initializeInputMaskBuffer(100); // Support up to 100 clients emit PreprocessingRoundExecuted(msg.sender, block.timestamp); } function gatherInputs() external override onlyDesignatedParty timedRoundTransition(Round.ClientInputMaskReservationRound, RESERVATION_TIMEOUT) atRound(Round.ClientInputMaskReservationRound) nextRound { emit ClientInputMaskReservationEvent(msg.sender, block.timestamp); } function initiateMPCComputation() external override onlyDesignatedParty timedRoundTransition(Round.CollectingClientInputRound, COLLECTION_TIMEOUT) atRound(Round.ClientInputsCollectionEndRound) nextRound { emit MPCTaskExecuted(_stoffelProgramHash, msg.sender, block.timestamp); } function publishOutputs() external override onlyDesignatedParty atRound(Round.MPCTaskExecutionEndRound) nextRound { // Implementation specific to your use case } // Allow clients to collect their outputs function collectOutput() external atRound(Round.ClientOutputCollectionRound) { uint256 output = clientOutputs[msg.sender]; require(output != 0, "No output for client"); // Return output to client } } ``` ## Best Practices 1. **Use timeouts**: Prevent deadlock with `timedRoundTransition` 2. **Emit events**: Enable off-chain monitoring of round transitions 3. **Validate inputs**: Check constraints before round transitions 4. **Test thoroughly**: Use Foundry's fuzzing for edge cases ## Next Steps * [Access Control](./access-control): Managing party roles * [Input Manager](./input-manager): Client input handling * [Overview](./overview): High-level architecture # Input Manager Source: https://docs.stoffelmpc.com/solidity-sdk/input-manager Contract for handling client input submission with privacy-preserving masking and ECDSA authentication. The `StoffelInputManager` contract handles client input submission for MPC computations, including mask reservation and ECDSA authentication. ## Overview ```solidity theme={null} abstract contract StoffelInputManager { // Manages client inputs with privacy-preserving masking } ``` ## How Input Masking Works Clients don't submit raw inputs on-chain (that would reveal them). Instead: 1. **MPC nodes generate input masks** during preprocessing 2. **Clients reserve mask indices** on-chain 3. **Clients submit masked inputs**: `masked = input + mask` 4. **MPC nodes unmask** during computation using their mask shares ``` Client Input: 42 Mask: 17 Masked Input: 59 ← This goes on-chain (reveals nothing about 42) ``` ## Data Structures ### MaskedInput ```solidity theme={null} struct MaskedInput { uint256 index; // Reserved mask index uint256 maskedInput; // Input XOR/+ mask } ``` ### Inputs ```solidity theme={null} struct Inputs { bytes publicInputs; // Optional public parameters MaskedInput[] maskedInputs; // Array of masked secret inputs } ``` ### Outputs ```solidity theme={null} struct Outputs { bytes publicOutputs; // Computation results mapping(address => mapping(address => bool)) sharesReceived; // Tracks: client => party => received } ``` ## Storage ```solidity theme={null} // Maps index to reserving client mapping(uint256 => address) public reservedInputIndices; // Total indices available uint256 public nTotalIndices; // Remaining unreserved indices uint256 public nIndicesLeft; // Client inputs storage mapping(address => MaskedInput) public clientInputs; ``` ## Functions ### initializeInputMaskBuffer Sets up the input mask buffer. Called by designated party during preprocessing. ```solidity theme={null} function initializeInputMaskBuffer(uint256 nIndicesToReserve) external onlyDesignatedParty { nTotalIndices = nIndicesToReserve; nIndicesLeft = nIndicesToReserve; } ``` ### reserveInputMask Clients call this to reserve an input mask index. ```solidity theme={null} function reserveInputMask(uint256 indexToReserve) external { require(indexToReserve < nTotalIndices, "Index out of bounds"); require(reservedInputIndices[indexToReserve] == address(0), "Index already reserved"); require(nIndicesLeft > 0, "No indices left"); reservedInputIndices[indexToReserve] = msg.sender; nIndicesLeft--; emit InputMaskReserved(msg.sender, indexToReserve); } ``` ### submitMaskedInput Clients submit their masked input using a reserved index. ```solidity theme={null} function submitMaskedInput(uint256 maskedInput, uint256 reservedIndex) external { require(reservedInputIndices[reservedIndex] == msg.sender, "Not your reserved index"); clientInputs[msg.sender] = MaskedInput({ index: reservedIndex, maskedInput: maskedInput }); // Unreserve the index (one-time use) reservedInputIndices[reservedIndex] = address(0); emit MaskedInputSubmitted(msg.sender, reservedIndex); } ``` ### authenticateClient MPC nodes use this for off-chain client authentication via ECDSA. ```solidity theme={null} function authenticateClient( uint256 requestIndex, address clientAddr, bytes calldata signature ) external view returns (bool) { // Construct the message hash bytes32 messageHash = keccak256(abi.encode(requestIndex)); bytes32 ethSignedHash = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash) ); // Recover signer from signature address recovered = recoverSigner(ethSignedHash, signature); return recovered == clientAddr; } ``` ### getClientInput Retrieve a client's submitted input. ```solidity theme={null} function getClientInput(address client) external view returns (MaskedInput memory) { return clientInputs[client]; } ``` ### hasClientSubmitted Check if a client has submitted their input. ```solidity theme={null} function hasClientSubmitted(address client) external view returns (bool) { return clientInputs[client].maskedInput != 0 || clientInputs[client].index != 0; } ``` ## Events ```solidity theme={null} event InputMaskReserved(address indexed client, uint256 indexed index); event MaskedInputSubmitted(address indexed client, uint256 indexed index); event ClientAuthenticated(address indexed client, uint256 indexed requestIndex); ``` ## Client Workflow ### 1. Reserve a Mask Index ```javascript theme={null} // Client reserves index 5 await coordinator.reserveInputMask(5); ``` ### 2. Get the Mask (Off-Chain) ```javascript theme={null} // Client contacts MPC nodes to get their mask // This happens off-chain via the Rust SDK const mask = await mpcClient.getInputMask(5); ``` ### 3. Compute Masked Input ```javascript theme={null} // Client masks their secret input const secretInput = 42n; const maskedInput = secretInput + mask; // or XOR depending on protocol ``` ### 4. Submit Masked Input ```javascript theme={null} // Client submits on-chain await coordinator.submitMaskedInput(maskedInput, 5); ``` ### 5. MPC Nodes Unmask During computation, MPC nodes: 1. Read `maskedInput` from contract 2. Subtract their mask share 3. Proceed with MPC on the unmasked value ## Authentication Flow For off-chain operations, clients prove their identity: ```javascript theme={null} // Client signs a request const requestIndex = 12345; const messageHash = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode(['uint256'], [requestIndex]) ); const signature = await wallet.signMessage(ethers.utils.arrayify(messageHash)); // MPC node verifies on-chain const isValid = await coordinator.authenticateClient( requestIndex, clientAddress, signature ); ``` ## Example: Complete Input Flow ```solidity theme={null} contract SecureVoting is StoffelCoordinator { mapping(address => bool) public hasVoted; function vote(uint256 maskedVote, uint256 maskIndex) external { require(!hasVoted[msg.sender], "Already voted"); require(currentRound == Round.CollectingClientInputRound, "Not collecting"); // Verify client reserved this index require(reservedInputIndices[maskIndex] == msg.sender, "Wrong index"); // Submit the masked vote clientInputs[msg.sender] = MaskedInput({ index: maskIndex, maskedInput: maskedVote }); hasVoted[msg.sender] = true; reservedInputIndices[maskIndex] = address(0); emit MaskedInputSubmitted(msg.sender, maskIndex); } } ``` ## Security Considerations ### Index Reservation * Each index can only be reserved once * Prevents double-spending of masks * Clients should reserve early to ensure availability ### Mask Uniqueness * Each mask is used exactly once * After submission, the index is unreserved * Prevents mask reuse attacks ### Authentication * ECDSA signatures verify client identity * Prevents impersonation in off-chain communications * Message includes unique `requestIndex` to prevent replay ### Input Privacy * Only masked values appear on-chain * Raw inputs never touch the blockchain * Privacy depends on MPC node security (threshold trust) ## Next Steps * [StoffelCoordinator](./coordinator): State machine details * [Access Control](./access-control): Role management * [Overview](./overview): Architecture overview # Solidity SDK Overview Source: https://docs.stoffelmpc.com/solidity-sdk/overview Smart contracts for coordinating MPC computations on-chain with trustless orchestration, verifiable inputs, and guaranteed outputs. The Stoffel Solidity SDK provides smart contracts for coordinating MPC computations on-chain. It enables trustless orchestration of multi-party computation with verifiable input collection and output distribution. ## Purpose On-chain coordination solves key challenges in MPC deployments: * **Trustless Setup**: No single party controls the computation lifecycle * **Verifiable Inputs**: Clients prove they submitted valid masked inputs * **Guaranteed Outputs**: Results are published on-chain for all participants * **Timeout Handling**: Automatic round progression prevents deadlock ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Blockchain │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ StoffelCoordinator Contract │ │ │ │ │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │ │ │ │ StoffelAccess │ │ StoffelInputManager │ │ │ │ │ │ Control │ │ │ │ │ │ │ │ - Party roles │ │ - Mask reservation │ │ │ │ │ │ - Permissions │ │ - Input submission │ │ │ │ │ └─────────────────┘ │ - Client authentication │ │ │ │ │ └─────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ MPC Nodes │◄────────────►│ Clients │ │ (Servers) │ │ (App Users) │ └──────────────┘ └──────────────┘ ``` ## Components ### StoffelCoordinator The main abstract contract implementing a 7-phase state machine for MPC orchestration. Subclass this to create your specific computation coordinator. [Learn more →](./coordinator) ### StoffelAccessControl Role-based access control for MPC parties: * **PARTY\_ROLE**: Assigned to MPC compute nodes * **DESIGNATED\_PARTY\_ROLE**: Elevated privileges for orchestration [Learn more →](./access-control) ### StoffelInputManager Client input handling: * Input mask reservation system * Masked input submission and storage * ECDSA-based client authentication [Learn more →](./input-manager) ## Quick Start ### 1. Create Your Coordinator ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {StoffelCoordinator} from "stoffel-solidity-sdk/StoffelCoordinator.sol"; contract MyComputation is StoffelCoordinator { constructor( bytes32 programHash, uint256 n, uint256 t, address designatedParty, address[] memory mpcNodes ) StoffelCoordinator(programHash, n, t, designatedParty, mpcNodes) {} function startPreprocessing() external override atRound(Round.PreprocessingRound) { // Initialize preprocessing nextRound(); } function gatherInputs() external override atRound(Round.ClientInputMaskReservationRound) { // Move to input collection nextRound(); } function initiateMPCComputation() external override atRound(Round.CollectingClientInputRound) { // Start MPC execution nextRound(); } function publishOutputs() external override atRound(Round.MPCTaskExecutionRound) { // Distribute results nextRound(); } } ``` ### 2. Deploy ```bash theme={null} forge create MyComputation \ --constructor-args \ 0x1234... \ # program hash 5 \ # n parties 1 \ # threshold 0xDesignated... \ "[0xNode1..., 0xNode2..., ...]" ``` ### 3. Coordinate Computation ```javascript theme={null} // Designated party starts preprocessing await coordinator.startPreprocessing(); // Clients reserve input masks await coordinator.reserveInputMask(0); // Clients submit masked inputs await coordinator.submitMaskedInput(maskedValue, reservedIndex); // MPC nodes execute off-chain // ... // Publish results on-chain await coordinator.publishOutputs(); ``` ## State Machine The coordinator follows a strict 7-phase lifecycle: ``` PreprocessingRound (0) │ ▼ ClientInputMaskReservationRound (1) │ ▼ CollectingClientInputRound (2) │ ▼ ClientInputsCollectionEndRound (3) │ ▼ MPCTaskExecutionRound (4) │ ▼ MPCTaskExecutionEndRound (5) │ ▼ ClientOutputCollectionRound (6) ``` See [StoffelCoordinator](./coordinator) for detailed round descriptions. ## Integration with Rust SDK The Solidity SDK works with the Rust SDK's MPCaaS architecture: 1. **Deploy coordinator** on-chain 2. **MPC servers** watch for round transitions 3. **Clients** submit inputs through the contract 4. **Servers** perform off-chain computation 5. **Designated party** publishes outputs on-chain ```rust theme={null} // Rust SDK: Watch for contract events let client = StoffelClient::builder() .with_coordinator_contract("0x...") .connect() .await?; ``` ## Security Considerations ### Byzantine Fault Tolerance The coordinator enforces `n >= 3t + 1`: * `n`: Number of MPC parties * `t`: Maximum faulty/malicious parties ### Input Privacy * Clients submit **masked** inputs, not raw values * Input masks are pre-generated by MPC nodes * Only the threshold can reconstruct original values ### Access Control * Only authorized parties can trigger round transitions * Designated party has additional privileges * Party count cannot drop below threshold ## Development ### Build ```bash theme={null} cd Stoffel-solidity-SDK forge build ``` ### Test ```bash theme={null} forge test -vvv ``` ### Deploy ```bash theme={null} forge script script/Deploy.s.sol --broadcast ``` ## Next Steps * [StoffelCoordinator](./coordinator): State machine details * [Access Control](./access-control): Role management * [Input Manager](./input-manager): Client input handling * [MPC Protocols](../mpc-protocols/overview): Underlying cryptography # Security Best Practices Source: https://docs.stoffelmpc.com/solidity-sdk/security Security considerations for MPC applications including access control, input integrity, and threshold configuration. This guide covers essential security considerations when building MPC applications with the Stoffel Solidity SDK. ## Overview MPC applications have unique security requirements beyond typical smart contracts. The security of the entire system depends on: 1. **Access control** - Who can trigger state transitions 2. **Input integrity** - Ensuring inputs are properly masked and validated 3. **Round sequencing** - Maintaining correct state machine flow 4. **Threshold configuration** - Proper n/t settings for Byzantine fault tolerance ## Access Control ### Why `onlyDesignatedParty` Matters The designated party role controls the MPC lifecycle. Unauthorized access could: * Skip preprocessing, causing computation failures * Prematurely end input collection, excluding legitimate clients * Trigger computation with insufficient inputs * Publish invalid outputs ```solidity theme={null} // ALWAYS protect lifecycle methods function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) { // ... } ``` Never remove the `onlyDesignatedParty` modifier from lifecycle methods. This is the primary defense against unauthorized state manipulation. ### Party Role Management ```solidity theme={null} // Safe party addition function addParty(address party) external onlyDesignatedParty { require(getPartyCount() < nParties, "Max parties reached"); _grantRole(PARTY_ROLE, party); } // Safe party removal - maintains threshold function removeParty(address party) external onlyDesignatedParty { require(getPartyCount() > threshold + 1, "Would violate threshold"); _revokeRole(PARTY_ROLE, party); } ``` ### Designated Party Security | Risk | Mitigation | | -------------------------- | -------------------------------- | | Private key compromise | Use hardware wallet or multi-sig | | Single point of failure | Consider time-locked transfers | | Malicious designated party | Implement governance controls | ```solidity theme={null} // Consider using a timelock for designated party transfer function initiateDesignatedPartyTransfer(address newParty) external onlyDesignatedParty { pendingDesignatedParty = newParty; transferUnlocksAt = block.timestamp + 2 days; } function completeDesignatedPartyTransfer() external { require(block.timestamp >= transferUnlocksAt, "Timelock active"); _transferDesignatedParty(pendingDesignatedParty); } ``` ## Threshold Configuration ### The n >= 3t + 1 Rule HoneyBadger MPC requires `n >= 3t + 1` for Byzantine fault tolerance: | n (parties) | t (threshold) | Tolerates | Valid? | | ----------- | ------------- | --------- | -------------- | | 4 | 1 | 1 faulty | Yes (4 >= 4) | | 5 | 1 | 1 faulty | Yes (5 >= 4) | | 7 | 2 | 2 faulty | Yes (7 >= 7) | | 10 | 3 | 3 faulty | Yes (10 >= 10) | | 5 | 2 | - | No (5 \< 7) | ```solidity theme={null} constructor(uint256 n, uint256 t, ...) { require(n >= 3 * t + 1, "Invalid threshold configuration"); // ... } ``` A threshold of `t` means the system tolerates up to `t` malicious or faulty parties. Higher thresholds require more parties but provide stronger security guarantees. ### Production Recommendations | Environment | Recommended Config | Reasoning | | ----------- | ------------------ | -------------------------- | | Development | n=4, t=1 | Minimum viable for testing | | Staging | n=5, t=1 | Allows for node failures | | Production | n=7+, t=2+ | Higher fault tolerance | ## Input Handling ### Why Input Masking is Required Raw inputs on-chain would be visible to everyone. Masking ensures privacy: ``` Client's secret: 42 Random mask: 17 On-chain value: 59 ← Reveals nothing about 42 ``` ```solidity theme={null} // NEVER accept raw inputs function submitInput(uint256 rawInput) external { // DANGEROUS! // Raw input visible on-chain to everyone } // ALWAYS require masked inputs function submitMaskedInput(uint256 maskedInput, uint256 reservedIndex) external { require(reservedInputIndices[reservedIndex] == msg.sender, "Invalid reservation"); // maskedInput reveals nothing without the mask } ``` ### Input Count Validation Always validate sufficient inputs before computation: ```solidity theme={null} function initiateMPCComputation() external override onlyDesignatedParty atRound(Round.ClientInputsCollectionEndRound) { // Validate minimum inputs require(currentInputCount >= minimumRequiredInputs, "Insufficient inputs"); // Optional: Validate maximum to prevent DoS require(currentInputCount <= maxInputs, "Too many inputs"); _nextRound(); } ``` ### Replay Attack Prevention Each input mask can only be used once: ```solidity theme={null} function submitMaskedInput(uint256 maskedInput, uint256 reservedIndex) external { // Check ownership require(reservedInputIndices[reservedIndex] == msg.sender, "Not your index"); // Store input clientInputs[msg.sender] = MaskedInput({ index: reservedIndex, maskedInput: maskedInput }); // CRITICAL: Invalidate the index to prevent reuse reservedInputIndices[reservedIndex] = address(0); } ``` Failing to invalidate used indices allows mask reuse attacks, which can leak information about client inputs. ## Round State Machine ### Why Rounds Must Be Sequential The round state machine ensures: 1. Preprocessing completes before inputs are collected 2. All inputs are gathered before computation 3. Computation finishes before outputs are published ``` PreprocessingRound → ClientInputMaskReservationRound → CollectingClientInputRound ↓ ↓ ClientInputsCollectionEndRound → MPCTaskExecutionRound → MPCTaskExecutionEndRound ↓ ClientOutputCollectionRound ``` ### The `atRound` Modifier Always use `atRound` to enforce correct sequencing: ```solidity theme={null} modifier atRound(Round expectedRound) { require(currentRound == expectedRound, "Invalid round"); _; } // Correct: enforces round ordering function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) { _nextRound(); } // DANGEROUS: allows skipping rounds function startPreprocessing() external override onlyDesignatedParty { // Missing atRound! _nextRound(); } ``` ### Common Round Pitfalls | Pitfall | Consequence | Prevention | | ------------------ | -------------------------- | ----------------------------------------- | | Skipping rounds | Missing preprocessing data | Always use `atRound` modifier | | Re-entering rounds | State corruption | Use `_nextRound()` only once per function | | Stuck in round | Computation blocked | Implement timeout mechanisms | ### Timeout Handling For production systems, implement timeouts to handle stuck states: ```solidity theme={null} uint256 public roundStartTime; uint256 public constant ROUND_TIMEOUT = 1 hours; modifier timedRound() { require(block.timestamp <= roundStartTime + ROUND_TIMEOUT, "Round timed out"); _; } function _nextRound() internal { currentRound = Round(uint(currentRound) + 1); roundStartTime = block.timestamp; // Reset timeout } // Emergency function if round times out function cancelComputation() external onlyDesignatedParty { require(block.timestamp > roundStartTime + ROUND_TIMEOUT, "Not timed out"); // Reset or refund logic } ``` ## Common Vulnerabilities ### 1. Insufficient Input Validation ```solidity theme={null} // VULNERABLE: No validation of input bounds function submitMaskedInput(uint256 maskedInput, uint256 index) external { clientInputs[msg.sender] = MaskedInput(index, maskedInput); } // SECURE: Validates index bounds and ownership function submitMaskedInput(uint256 maskedInput, uint256 index) external { require(index < nTotalIndices, "Index out of bounds"); require(reservedInputIndices[index] == msg.sender, "Not your index"); clientInputs[msg.sender] = MaskedInput(index, maskedInput); reservedInputIndices[index] = address(0); } ``` ### 2. Missing Round Checks ```solidity theme={null} // VULNERABLE: Can be called at any time function submitMaskedInput(uint256 maskedInput, uint256 index) external { // ... } // SECURE: Enforces correct round function submitMaskedInput(uint256 maskedInput, uint256 index) external atRound(Round.CollectingClientInputRound) { // ... } ``` ### 3. Incorrect Threshold Configuration ```solidity theme={null} // VULNERABLE: Allows invalid configurations constructor(uint256 n, uint256 t, ...) { nParties = n; threshold = t; // No validation! } // SECURE: Validates before setting constructor(uint256 n, uint256 t, ...) { require(n >= 3 * t + 1, "Invalid: n must be >= 3t + 1"); require(n >= 4, "Minimum 4 parties required"); require(t >= 1, "Threshold must be at least 1"); nParties = n; threshold = t; } ``` ### 4. Reentrancy in Input Submission ```solidity theme={null} // VULNERABLE: State updated after external call function submitMaskedInput(uint256 maskedInput, uint256 index) external { // External call before state update _notifySubmission(msg.sender); // Could reenter! reservedInputIndices[index] = address(0); } // SECURE: Checks-Effects-Interactions pattern function submitMaskedInput(uint256 maskedInput, uint256 index) external { // Checks require(reservedInputIndices[index] == msg.sender, "Not your index"); // Effects (state changes first) reservedInputIndices[index] = address(0); clientInputs[msg.sender] = MaskedInput(index, maskedInput); // Interactions (external calls last) _notifySubmission(msg.sender); } ``` ### 5. Missing Events for Critical Actions ```solidity theme={null} // VULNERABLE: No audit trail function addParty(address party) external onlyDesignatedParty { _grantRole(PARTY_ROLE, party); } // SECURE: Emits events for monitoring event PartyAdded(address indexed party, address indexed addedBy, uint256 timestamp); function addParty(address party) external onlyDesignatedParty { _grantRole(PARTY_ROLE, party); emit PartyAdded(party, msg.sender, block.timestamp); } ``` ## Security Checklist Before deploying your MPC contract, verify: ### Access Control * [ ] All lifecycle methods have `onlyDesignatedParty` modifier * [ ] Party management functions validate threshold constraints * [ ] Designated party key is secured (hardware wallet/multi-sig) ### Input Handling * [ ] Input masks are initialized in preprocessing * [ ] Index reservations are validated before submission * [ ] Used indices are invalidated after submission * [ ] Minimum input count is enforced before computation ### Round Management * [ ] All state-changing functions use `atRound` modifier * [ ] `_nextRound()` is called exactly once per lifecycle method * [ ] Timeout mechanisms exist for stuck states ### General * [ ] Threshold configuration satisfies `n >= 3t + 1` * [ ] Critical events are emitted for monitoring * [ ] Checks-Effects-Interactions pattern is followed * [ ] Contract has been tested with edge cases ## Next Steps * [Template Guide](./template-guide) - Step-by-step implementation * [StoffelCoordinator](./coordinator) - Full API reference * [Access Control](./access-control) - Role management details * [Input Manager](./input-manager) - Input handling details # Using the Solidity Templates Source: https://docs.stoffelmpc.com/solidity-sdk/template-guide Guide to creating on-chain MPC applications with Foundry or Hardhat templates via the Stoffel CLI. This guide walks you through using the Stoffel CLI's Solidity templates to build on-chain coordinated MPC applications. ## Choosing a Template Stoffel provides two Solidity templates: | Template | Framework | Best For | | ------------------ | --------- | ------------------------------------------------------------ | | `solidity-foundry` | Foundry | Rust developers, CI/CD pipelines, fast compilation | | `solidity-hardhat` | Hardhat | JavaScript/TypeScript developers, existing Hardhat workflows | Both templates generate identical contract code - choose based on your team's preferred tooling. ## Creating a Project ```bash theme={null} # Foundry template (recommended for most use cases) stoffel init my-mpc-app --template solidity-foundry # Hardhat template stoffel init my-mpc-app --template solidity-hardhat ``` ## Generated Project Structure ### Foundry Template ``` my-mpc-app/ ├── Cargo.toml # Rust workspace root ├── Makefile # Build orchestration ├── app/ │ └── src/main.rs # Rust application for MPC operations ├── crates/bindings/ # Auto-generated contract bindings ├── contracts/ │ ├── foundry.toml # Foundry configuration │ ├── src/ │ │ └── MyMPCApp.sol # Your MPC coordinator contract │ ├── test/ │ │ └── MyMPCApp.t.sol # Foundry tests │ └── script/ │ └── Deploy.s.sol # Deployment script └── stoffel/ ├── Stoffel.toml # MPC configuration └── src/ └── program.stfl # StoffelLang MPC program ``` ### Hardhat Template ``` my-mpc-app/ ├── package.json ├── hardhat.config.ts ├── contracts/ │ └── MyMPCApp.sol # Your MPC coordinator contract ├── test/ │ └── MyMPCApp.test.ts # TypeScript tests ├── scripts/ │ └── deploy.ts # Deployment script └── stoffel/ ├── Stoffel.toml └── src/ └── program.stfl ``` ## Understanding MyMPCApp.sol The generated `MyMPCApp.sol` extends `StoffelCoordinator` and requires you to implement 4 abstract methods that control the MPC lifecycle. ### The 4 Required Methods ```solidity theme={null} contract MyMPCApp is StoffelCoordinator { // 1. Initialize preprocessing (input masks, cryptographic material) function startPreprocessing() external override; // 2. Enable clients to submit inputs function gatherInputs() external override; // 3. Trigger the off-chain MPC computation function initiateMPCComputation() external override; // 4. Publish computation results on-chain function publishOutputs() external override; } ``` ## Implementing Each Method ### 1. startPreprocessing() Called by the designated party to initialize the preprocessing phase. ```solidity theme={null} function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) { // Initialize the input mask buffer // The parameter is the number of client inputs you expect this.initialzeInputMaskBuffer(10); // Allow up to 10 clients emit PreprocessingStarted(msg.sender, block.timestamp); _nextRound(); } ``` You MUST call `initialzeInputMaskBuffer()` during preprocessing. Without this, clients cannot reserve input masks and submit inputs. ### 2. gatherInputs() Transitions the contract to accept client inputs. ```solidity theme={null} function gatherInputs() external override onlyDesignatedParty atRound(Round.ClientInputMaskReservationRound) { // Optional: Add any validation or setup logic emit InputGatheringStarted(msg.sender, block.timestamp); _nextRound(); } ``` After this method executes, clients can: 1. Call `reserveInputMask(index)` to reserve a slot 2. Request their input mask from MPC nodes off-chain 3. Call `submitMaskedInput(maskedInput, reservedIndex)` to submit ### 3. initiateMPCComputation() Triggers the off-chain MPC computation. ```solidity theme={null} function initiateMPCComputation() external override onlyDesignatedParty atRound(Round.ClientInputsCollectionEndRound) { // Validate you have enough inputs require(currentInputCount >= requiredInputCount, "Not enough inputs"); emit MPCComputationInitiated(msg.sender, currentInputCount, block.timestamp); _nextRound(); } ``` MPC nodes listen for the `MPCComputationInitiated` event to begin the off-chain computation. Ensure your event includes all necessary context. ### 4. publishOutputs() Called after the MPC computation completes to publish results. ```solidity theme={null} function publishOutputs() external override onlyDesignatedParty atRound(Round.MPCTaskExecutionEndRound) { // Store public outputs (computed off-chain by MPC nodes) // publicOutputs = _computedOutputs; emit OutputsPublished(msg.sender, publicOutputs, block.timestamp); _nextRound(); } ``` ## Complete Implementation Example Here's a complete example for a secure voting application: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {StoffelCoordinator} from "@stoffel/StoffelCoordinator.sol"; contract SecureVoting is StoffelCoordinator { uint256 public requiredVoters; uint256 public currentVoteCount; uint256 public winningOption; event VotingStarted(uint256 requiredVoters); event VoteReceived(address indexed voter); event VotingCompleted(uint256 winningOption, uint256 totalVotes); constructor( bytes32 _programHash, uint256 _n, uint256 _t, address _designatedParty, address[] memory _nodes, uint256 _requiredVoters ) StoffelCoordinator(_programHash, _n, _t, _designatedParty, _nodes) { requiredVoters = _requiredVoters; } function startPreprocessing() external override onlyDesignatedParty atRound(Round.PreprocessingRound) { // Reserve input masks for all expected voters this.initialzeInputMaskBuffer(requiredVoters); emit VotingStarted(requiredVoters); _nextRound(); } function gatherInputs() external override onlyDesignatedParty atRound(Round.ClientInputMaskReservationRound) { _nextRound(); } function initiateMPCComputation() external override onlyDesignatedParty atRound(Round.ClientInputsCollectionEndRound) { require(currentVoteCount >= requiredVoters, "Not enough votes"); emit MPCComputationInitiated(msg.sender, currentVoteCount, block.timestamp); _nextRound(); } function publishOutputs() external override onlyDesignatedParty atRound(Round.MPCTaskExecutionEndRound) { // winningOption is set by submitResult() called separately emit VotingCompleted(winningOption, currentVoteCount); _nextRound(); } // Hook called when a vote is submitted function _onInputReceived(address voter, uint256) internal { currentVoteCount++; emit VoteReceived(voter); } function submitResult(uint256 _result) external onlyDesignatedParty { winningOption = _result; } } ``` ## Testing Your Contract ### Foundry Tests ```bash theme={null} cd contracts forge test forge test -vvv # Verbose output ``` Example test file (`test/MyMPCApp.t.sol`): ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "forge-std/Test.sol"; import "../src/MyMPCApp.sol"; contract MyMPCAppTest is Test { MyMPCApp public app; address designatedParty = address(1); address[] nodes; function setUp() public { nodes = new address[](5); for (uint i = 0; i < 5; i++) { nodes[i] = address(uint160(100 + i)); } vm.prank(designatedParty); app = new MyMPCApp( keccak256("test-program"), 5, // n parties 1, // threshold designatedParty, nodes ); } function test_StartPreprocessing() public { vm.prank(designatedParty); app.startPreprocessing(); assertEq( uint(app.getCurrentRound()), uint(StoffelCoordinator.Round.ClientInputMaskReservationRound) ); } } ``` ### Hardhat Tests ```bash theme={null} npx hardhat test npx hardhat test --grep "specific test" ``` ## Deployment ### Foundry Deployment ```bash theme={null} cd contracts # Deploy to local network forge script script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast # Deploy to testnet (e.g., Sepolia) forge script script/Deploy.s.sol \ --rpc-url $SEPOLIA_RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ --verify ``` ### Hardhat Deployment ```bash theme={null} # Deploy to local network npx hardhat run scripts/deploy.ts --network localhost # Deploy to testnet npx hardhat run scripts/deploy.ts --network sepolia ``` ## End-to-End Workflow Here's the complete flow for running an MPC computation: ``` 1. Deploy Contract └─ Constructor sets up parties, threshold, program hash 2. Preprocessing (Round 0) └─ Designated party calls startPreprocessing() └─ MPC nodes generate input masks off-chain └─ Contract transitions to Round 1 3. Input Mask Reservation (Round 1) └─ Designated party calls gatherInputs() └─ Contract transitions to Round 2 4. Input Collection (Round 2) └─ Clients call reserveInputMask(index) └─ Clients request mask from MPC nodes (off-chain) └─ Clients call submitMaskedInput(masked, index) └─ When ready, transition to Round 3 5. Computation Initiation (Round 3) └─ Designated party calls initiateMPCComputation() └─ MPC nodes see event, begin off-chain computation └─ Contract transitions to Round 4 6. MPC Execution (Round 4) └─ Off-chain: MPC nodes run StoffelLang program └─ Off-chain: Nodes compute on secret-shared inputs └─ When complete, transition to Round 5 7. Output Publishing (Round 5) └─ Designated party calls publishOutputs() └─ Public results stored on-chain └─ Contract transitions to Round 6 8. Output Collection (Round 6) └─ Clients retrieve their results └─ Computation complete! ``` ## Handling Optional Public State When clients submit masked inputs via `submitMaskedInput`, your application may also need to track associated **public state** - data that doesn't need privacy protection but is logically tied to the input. This pattern is optional. Many applications only need the masked input itself and can skip this section entirely. ### When You Need Public State | Use Case | Public State Example | | ----------- | --------------------------------------------------- | | Voting | Voter's preferred language for result notifications | | Auction | Bidder's display name (not the bid amount) | | Survey | Respondent's demographic category | | Computation | Input metadata (timestamp, format version) | ### When You Don't Need Public State * Simple computations where only the masked value matters * Applications where all metadata is handled off-chain * Minimal contracts that only need MPC results ### Implementation Pattern Create a wrapper function that calls `submitMaskedInput` and stores additional public metadata: ```solidity theme={null} contract MyMPCApp is StoffelCoordinator { // Optional: Track public state alongside masked inputs mapping(address => bytes) public clientMetadata; event InputWithMetadata(address indexed client, uint256 reservedIndex, bytes metadata); /// @notice Submit masked input with optional public metadata /// @param maskedInput The masked value (raw input + mask) /// @param reservedIndex The reserved index /// @param metadata Optional public data associated with this input function submitMaskedInputWithMetadata( uint256 maskedInput, uint256 reservedIndex, bytes calldata metadata ) external { // Call parent to handle the masked input this.submitMaskedInput(maskedInput, reservedIndex); // Store optional public metadata if (metadata.length > 0) { clientMetadata[msg.sender] = metadata; } emit InputWithMetadata(msg.sender, reservedIndex, metadata); } } ``` Public state is stored on-chain and visible to everyone. Only use it for non-sensitive metadata. The actual secret input remains protected by the masking mechanism. ### Example: Voting with Voter Preferences ```solidity theme={null} contract SecureVotingWithPreferences is StoffelCoordinator { struct VoterPrefs { string preferredLanguage; bool wantsEmailNotification; } mapping(address => VoterPrefs) public voterPreferences; function submitVoteWithPreferences( uint256 maskedVote, uint256 reservedIndex, string calldata language, bool emailNotify ) external atRound(Round.CollectingClientInputRound) { // Submit the private vote this.submitMaskedInput(maskedVote, reservedIndex); // Store public preferences (not sensitive) voterPreferences[msg.sender] = VoterPrefs({ preferredLanguage: language, wantsEmailNotification: emailNotify }); } } ``` ## Next Steps * [Security Best Practices](./security) - Essential safety considerations * [StoffelCoordinator Reference](./coordinator) - Full API documentation * [Access Control](./access-control) - Managing MPC parties * [Input Manager](./input-manager) - Client input handling # Compilation Source: https://docs.stoffelmpc.com/stoffel-lang/compilation How StoffelLang source is validated, compiled, optimized, disassembled, and executed in current Stoffel programs. StoffelLang source files (`.stfl`) compile to portable Stoffel bytecode files (`.stflb`). The current compiler lives in `crates/stoffel-lang`; the primary user-facing entry points are the `stoffel` CLI and Rust SDK. ## Pipeline Compilation pipeline from StoffelLang source through parsing, type checking, lowering, optimization, and .stflb bytecode. The compiler validates `.stfl` source, lowers it to VM function metadata and instructions, then writes the `.stflb` artifact that the CLI, SDK, and VM can load. ## Validate without writing bytecode ```bash theme={null} stoffel check stoffel check src/main.stfl stoffel check src stoffel check --print-ir ``` `check` reads project defaults from `Stoffel.toml` unless you pass a source path. MPC settings can be overridden: ```bash theme={null} stoffel check --backend honeybadger --field bls12-381 --parties 5 --threshold 1 ``` ## Build a project ```bash theme={null} stoffel build stoffel build --release stoffel build --print-ir ``` `build` writes bytecode under `target/debug` or `target/release`. ## Compile a source file ```bash theme={null} stoffel compile src/main.stfl --output target/debug/main.stflb stoffel compile src/main.stfl -O3 --output target/release/main.stflb ``` When compiling one selected source file, use `--output` to choose the bytecode path. ## Optimization flags ```bash theme={null} stoffel compile src/main.stfl -O0 --output target/debug/main.stflb stoffel compile src/main.stfl -O 2 --output target/debug/main.stflb stoffel compile src/main.stfl --opt-level 3 --output target/release/main.stflb stoffel build --optimize stoffel build --release ``` `--optimize` uses O2 unless `--release` selects O3. `--release` writes under `target/release` and uses O3 unless `--opt-level` is set. ## Disassemble bytecode ```bash theme={null} stoffel compile --disassemble target/debug/main.stflb ``` Use disassembly to confirm generated functions, instructions, and metadata before debugging runtime behavior. ## Run after compiling ```bash theme={null} stoffel run target/debug/main.stflb --entry main stoffel run src/main.stfl --input a=40 --input b=2 stoffel run --program-info ``` For local MPC runs, use the CLI local MPC path and provide any required client-slot inputs: ```bash theme={null} stoffel run --client-input 0=42 --parties 5 --threshold 1 ``` ## Validate repository examples The repository examples are runnable programs under [`crates/stoffel-lang/examples/`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples). From the Stoffel repository root, compile the examples and run local-only examples through the VM with: ```bash theme={null} cd crates/stoffel-lang ./examples/validate_examples.sh ``` Compiled bytecode is written to `examples/dist/`. Use the [runnable examples guide](./examples) to choose examples by task. ## Common errors ### `secret` placement Use `secret` inside type annotations for share values: ```stoffel theme={null} def compute(x: secret int64) -> secret int64: var doubled: secret int64 = x * 2 return doubled ``` Do not use `secret` as a declaration modifier before `def` or `var`. The accepted form is `var x: secret int64 = ...`, not `secret var x = ...`. ### Secret control flow Do not branch on secret values. Reveal/open only the output you intend to disclose, or keep values as `Share` / `secret T` and send them to a client output path. ## See also * [StoffelLang Overview](./overview) * [Syntax and Examples](./syntax) * [Runnable Examples](./examples) * [Basic Usage](../getting-started/basic-usage) # Runnable Examples Source: https://docs.stoffelmpc.com/stoffel-lang/examples A guided path through runnable StoffelLang examples in the Stoffel repository. The Stoffel repository includes runnable StoffelLang programs under [`crates/stoffel-lang/examples/`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples). Use them to learn the language, read client-provided private inputs, compose MPC primitives, and adapt private workflows into your own app. ## Recommended path If you are new to StoffelLang, start with these examples in order: 1. [`local_control_flow`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_control_flow) — learn functions, loops, ranges, branching, and arithmetic. 2. [`local_collections`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_collections) — work with lists, indexing, appending, and length checks. 3. [`mpc_client_private_score`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_private_score) — read a client-provided private input and return an output share. 4. [`mpc_secure_comparison`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_secure_comparison) — study a reusable MPC primitive that reveals only a comparison bit. 5. [`mpc_histogram`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_histogram) or [`mpc_first_price_auction`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_first_price_auction) — see how primitives become app-shaped private workflows. This path starts with clear StoffelLang syntax, then moves into private inputs, explicit reveal boundaries, and larger MPC application patterns. ## Run the examples Clone the Stoffel repository and validate the examples from the repository root: ```bash theme={null} git clone https://github.com/Stoffel-Labs/stoffel.git cd stoffel/crates/stoffel-lang ./examples/validate_examples.sh ``` The validation script compiles the examples, runs local-only examples through the VM, and writes compiled bytecode to `examples/dist/`. A failure usually means the local CLI/compiler checkout is out of sync or the example needs a specific input shape from its README. ### Run a clear language example Start with a clear example when you want the lowest-friction check: ```bash theme={null} cd examples/local_control_flow stoffel check main.stfl stoffel run main.stfl ``` ### Run a client-input MPC example Read each example README before running MPC examples, because client slots and input values are example-specific. For [`mpc_client_private_score`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_private_score): ```bash theme={null} cd examples/mpc_client_private_score stoffel check main.stfl stoffel run main.stfl --client-input 0=42 --parties 5 --threshold 1 ``` ## Choose by job | Job | Start with | Why | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Learn StoffelLang syntax | [`local_control_flow`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_control_flow), [`local_collections`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_collections), [`local_nested_generics`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_nested_generics) | Clear programs isolate language mechanics before MPC value handling. | | Split code across files | [`language_policy_engine`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/language_policy_engine) | Shows imports, aliases, numeric widths, and app-shaped policy code. | | Model private inputs | [`mpc_client_private_score`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_private_score), [`mpc_client_federated_average`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_federated_average) | Shows `ClientStore` input slots and client output paths. | | Compute on shares | [`mpc_share_arithmetic`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_share_arithmetic), [`mpc_boolean_circuit`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_boolean_circuit), [`mpc_bitwise_share`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_bitwise_share) | Shows secret arithmetic, secret booleans, and share-oriented operators. | | Inspect runtime metadata | [`mpc_runtime_info`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_runtime_info) | Shows party count, threshold, backend, curve, field, and capabilities. | | Explore broad builtin coverage | [`mpc_share_toolkit`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_share_toolkit) | Collects Share, ClientStore, MpcOutput, and crypto builtin patterns. | ## Reuse MPC building blocks These examples are useful when you need source-level patterns for a private computation boundary: | Building block | Examples | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Secure comparison | [`mpc_secure_comparison`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_secure_comparison), [`mpc_compare_family`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_compare_family) | | Oblivious select / mux | [`mpc_select_minmax`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_select_minmax), [`mpc_mux_tree`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_mux_tree), [`bits/secret/oblivious_mux`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/bits/secret/oblivious_mux) | | Equality and membership | [`bits/secret/equality_check`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/bits/secret/equality_check), [`mpc_set_membership`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_set_membership), [`mpc_is_zero`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_is_zero) | | Range checks and clamps | [`mpc_range_check`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_range_check), [`mpc_clamp`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_clamp) | | Oblivious reads and writes | [`mpc_oblivious_read`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_oblivious_read), [`mpc_oblivious_write`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_oblivious_write), [`mpc_lookup_table`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_lookup_table) | | Sorting and order statistics | [`mpc_sorting_network`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_sorting_network), [`mpc_bitonic_sort`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_bitonic_sort), [`mpc_median`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_median), [`mpc_top_k`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_top_k) | | Secret integer bit operations | [`mpc_bit_decomposition`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_bit_decomposition), [`mpc_bitwise_int`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_bitwise_int), [`mpc_popcount_secret`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_popcount_secret), [`mpc_parity`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_parity) | | Fixed-point approximation | [`mpc_transcendental`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_transcendental), [`mpc_reciprocal`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_reciprocal), [`mpc_sqrt`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_sqrt) | When adapting these examples, copy the computation pattern, not the whole directory structure. Keep private inputs at `ClientStore`, keep intermediate values secret, and make reveal or client-output points explicit. ## Study larger workflows Use these after you understand the smaller building blocks: | Workflow | Examples | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Private analytics | [`mpc_histogram`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_histogram), [`mpc_mean`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_mean), [`mpc_variance`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_variance), [`mpc_covariance`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_covariance) | | Private inference | [`mpc_logistic_regression`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_logistic_regression), [`mpc_decision_tree`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_decision_tree), [`mpc_mlp_inference`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_mlp_inference), [`mpc_knn`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_knn) | | Auctions and voting | [`mpc_first_price_auction`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_first_price_auction), [`mpc_second_price_auction`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_second_price_auction), [`mpc_voting_tally`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_voting_tally), [`mpc_weighted_voting`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_weighted_voting) | | Private set workflows | [`mpc_set_intersection`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_set_intersection), [`mpc_set_cardinality`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_set_cardinality), [`mpc_dh_psi`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_dh_psi) | | Encrypted-data and client-IO patterns | [`mpc_aes128_circuit`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_aes128_circuit), [`mpc_aes128_ctr_client_io`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_aes128_ctr_client_io), [`mpc_aes128_cbc_client_io`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_aes128_cbc_client_io) | | Threshold crypto and AVSS | [`threshold_signatures/*`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/threshold_signatures), [`avss_certificate/*`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/avss_certificate), [`avss_share_auditor`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/avss_share_auditor) | Ordinary app logic should stay in the host application. The StoffelLang program should contain the private computation, the client input reads, and the explicit reveal or client-output boundary. Use the [Rust SDK examples](../rust-sdk/examples) when you are ready to embed a StoffelLang program in application code. ## Coverage matrix For complete language and builtin coverage, read [`crates/stoffel-lang/examples/COVERAGE.md`](https://github.com/Stoffel-Labs/stoffel/blob/main/crates/stoffel-lang/examples/COVERAGE.md). The matrix maps syntax, runtime semantics, ClientStore APIs, Share APIs, MPC metadata, crypto builtins, AVSS helpers, and RBC helpers to specific examples. ## See also * [StoffelLang Overview](./overview) * [Syntax and Examples](./syntax) * [Compilation](./compilation) * [Built-in Functions](../stoffel-vm/builtins) # FFI / C Bindings Source: https://docs.stoffelmpc.com/stoffel-lang/ffi Status and guidance for using StoffelLang from non-Rust environments. The primary supported application surfaces are the `stoffel` CLI and the Rust SDK. If you are writing C, C++, Python, or another host-language integration, start by treating the CLI-built `.stflb` artifact as the boundary: build with `stoffel build`, then hand that bytecode to the runtime surface your integration owns. ## Recommended path ```bash theme={null} stoffel check stoffel build stoffel compile --disassemble target/debug/.stflb ``` For application code that needs a stable executable API today, use the Rust SDK and expose your own host-language boundary around it. ## Compiler crate The compiler implementation lives in the `stoffel` repository under `crates/stoffel-lang`. Lower-level FFI work should validate against the current crate source before documenting C ABI details, because the app-facing docs should not promise unsupported package APIs. ## Source shape Use current StoffelLang syntax when compiling from any host language: ```stoffel theme={null} def main() -> int64: var private_score: secret int64 = ClientStore.take_share(0, 0) var adjusted = private_score + 25 return adjusted.reveal() ``` ## See also * [Compilation](./compilation) * [Rust SDK API](../rust-sdk/api) * [CLI Overview](../cli/overview) # StoffelLang Overview Source: https://docs.stoffelmpc.com/stoffel-lang/overview The current Stoffel language for clear local logic and MPC-aware computations compiled to Stoffel VM bytecode. StoffelLang is the application language compiled by the `stoffellang` crate in the `stoffel` repository. It uses Python-like indentation, `def` functions, `var` bindings, static type annotations, lists, objects, closures, and explicit MPC share APIs. Prefer examples from [`crates/stoffel-lang/examples/`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples), the generated project template, and the Rust SDK examples over older syntax outside the documented `def`/`var`/`.stflb` workflow. Use the [runnable examples guide](./examples) to choose examples by task. ## Design goals * Familiar Python-style control flow and indentation. * Static types with inference for local development ergonomics, including `secret T` annotations for share-typed private values. * Direct compilation to portable `.stflb` Stoffel VM bytecode. * Explicit share-oriented MPC APIs such as `Share.*`, `ClientStore.*`, `Mpc.*`, and `MpcOutput.*`, plus operator syntax for share arithmetic. * Host integration through the CLI and Rust SDK. ## A minimal program ```stoffel theme={null} def add(a: int64, b: int64) -> int64: return a + b def main() -> int64: return add(40, 2) ``` Compile and run it from a project or as a source file: ```bash theme={null} stoffel check src/main.stfl stoffel build stoffel run --entry main ``` ## Variables and types Use `var` for local bindings. Type annotations are optional when the compiler can infer the type. ```stoffel theme={null} def main() -> int64: var count: int64 = 42 var message = "hello" var ready = true discard message discard ready return count ``` Current primitive and runtime-facing types include: * signed integers: `int8`, `int16`, `int32`, `int64` * unsigned integers: `uint8`, `uint16`, `uint32`, `uint64` * `bool` * `string` * `None` / no-value returns * `Share` and `secret T` types such as `secret int64` for MPC secret shares * `list[T]` * object and closure values used by the VM runtime The `secret` keyword is part of type annotations. Use it on parameters, return types, local variables, list elements, and object fields when a value is represented as an MPC share: ```stoffel theme={null} def weighted_score(raw: secret int64, weight: int64) -> secret int64: var scaled: secret int64 = raw * weight return scaled + 10 ``` Do not put `secret` before `def` or `var`; write `var score: secret int64 = ...`, not `secret var score = ...`. ## Functions ```stoffel theme={null} def multiply(a: int64, b: int64) -> int64: return a * b def log_message(message: string) -> None: print(message) ``` The CLI and SDK default to executing `main`, but most commands can select another entry function: ```bash theme={null} stoffel run --entry multiply --input a=6 --input b=7 ``` ## Control flow ```stoffel theme={null} def fibonacci(n: int64) -> int64: if n <= 1: return n var previous: int64 = 0 var current: int64 = 1 var index: int64 = 2 while index <= n: var next: int64 = previous + current previous = current current = next index = index + 1 return current ``` Ranges and list iteration are supported: ```stoffel theme={null} def checksum(limit: int64) -> int64: var total: int64 = 0 for value in 1..limit: total += value return total ``` ## Lists ```stoffel theme={null} def main() -> int64: var scores: list[int64] = [] scores.append(72) scores.append(41) scores.append(93) scores[1] = scores[1] + 10 var total: int64 = 0 for score in scores: total += score return total + scores.len() ``` ## MPC share APIs StoffelLang models MPC values explicitly as share values. You can write those values with the generic `Share` type or with typed secret annotations such as `secret int64`, `secret bool`, or `secret fix64`. Use `ClientStore` to read client-provided shares, regular operators such as `+`, `-`, `*`, and `/` for arithmetic when they fit the value shape, and `reveal()`, `Share.open(...)`, or `MpcOutput.send_to_client` where your program intentionally reconstructs or delivers a result. ```stoffel theme={null} def add_private_values(a: secret int64, b: secret int64) -> int64: var sum = a + b return sum.reveal() ``` Method-style calls are also available when you want to call a specific share builtin directly: ```stoffel theme={null} def normalize(raw_score: secret int64) -> secret int64: var adjusted = raw_score.add_scalar(25) return adjusted * 2 ``` Boolean and fixed-point secret values use the same type-annotation pattern: ```stoffel theme={null} def gate(a: secret bool, b: secret bool) -> secret bool: var not_a: secret bool = 1 - a return not_a * b def half(value: secret fix64) -> secret fix64: return value / 2.0 ``` ## ClientStore inputs `ClientStore` exposes local or network client input slots to the program: ```stoffel theme={null} def main() -> int64: var client_count = ClientStore.get_number_clients() var share = ClientStore.take_share(0, 0) var opened: int64 = share.open() return opened + client_count ``` When running locally through the CLI, provide client-slot inputs with `--client-input`: ```bash theme={null} stoffel run --client-input 0=42 --parties 5 --threshold 1 ``` With the Rust SDK, use `.with_client_input(0, &[42_i64])`. ## Runtime metadata MPC programs can query runtime metadata and capabilities: ```stoffel theme={null} def main() -> int64: var score: int64 = Mpc.party_id() score = score + Mpc.n_parties() score = score + Mpc.threshold() if Mpc.has_capability("multiplication"): score = score + 100 discard Mpc.protocol_name() discard Mpc.curve() discard Mpc.field() discard Mpc.instance_id() return score ``` ## Compilation outputs The current bytecode extension is `.stflb`: ```bash theme={null} stoffel compile src/main.stfl --output target/debug/main.stflb stoffel compile --disassemble target/debug/main.stflb ``` `stoffel build` writes project bytecode under `target/debug` or `target/release` depending on the selected profile. ## Examples in the repository Runnable examples live under [`crates/stoffel-lang/examples/`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples). Start with the [examples guide](./examples) when you want source-level patterns for clear StoffelLang, client-provided private inputs, share arithmetic, reusable MPC building blocks, or larger private workflows. ## Next steps * [Syntax and Examples](./syntax) * [Runnable Examples](./examples) * [Compilation](./compilation) * [Built-in Functions](../stoffel-vm/builtins) * [Rust SDK Examples](../rust-sdk/examples) # Syntax and Examples Source: https://docs.stoffelmpc.com/stoffel-lang/syntax Current StoffelLang syntax for functions, variables, control flow, lists, Share values, and ClientStore inputs. StoffelLang uses Python-like indentation, `def` functions, `var` bindings, static types, lists, and explicit MPC share APIs. Programs compile to `.stflb` bytecode and are run through the `stoffel` CLI or Rust SDK. ## Comments ```stoffel theme={null} # Single-line comment def main() -> int64: var value = 42 # inline comment return value ``` ## Functions and entry point Functions use `def`. The default entry point is a function named `main`: ```stoffel theme={null} def add(a: int64, b: int64) -> int64: return a + b def main() -> int64: return add(40, 2) ``` Select another entry point with the CLI when needed: ```bash theme={null} stoffel run --entry add --input a=6 --input b=7 ``` ## Variables and primitive types Use `var` for local bindings. Type annotations are optional when inference is clear. ```stoffel theme={null} def main() -> int64: var count: int64 = 42 var message = "hello" var ready = true discard message discard ready return count ``` Common types: * signed integers: `int8`, `int16`, `int32`, `int64` * unsigned integers: `uint8`, `uint16`, `uint32`, `uint64` * `bool` * `string` * `Share` and typed secret values such as `secret int64` * `list[T]` * `None` for no-value returns ## Control flow ```stoffel theme={null} def fibonacci(n: int64) -> int64: if n <= 1: return n var previous: int64 = 0 var current: int64 = 1 var index: int64 = 2 while index <= n: var next: int64 = previous + current previous = current current = next index = index + 1 return current ``` Ranges and list iteration are supported: ```stoffel theme={null} def checksum(limit: int64) -> int64: var total: int64 = 0 for value in 1..limit: total += value return total ``` ## Lists ```stoffel theme={null} def main() -> int64: var scores: list[int64] = [] scores.append(72) scores.append(41) scores.append(93) scores[1] = scores[1] + 10 var total: int64 = 0 for score in scores: total += score return total + scores.len() ``` ## Share and `secret` values `Share` values represent private MPC values. You can also use the `secret` keyword in type annotations, such as `secret int64`, `secret bool`, or `secret fix64`, to declare typed share values. Keep sensitive values as shares while computing, then reveal, open, or send only the output you intend to disclose. `secret` is valid inside type annotations for parameters, returns, locals, list elements, and object fields. It is not a declaration modifier: use `var x: secret int64 = ...`, not `secret var x = ...`. ```stoffel theme={null} def normalize_score(raw_score: secret int64) -> secret int64: var adjusted = raw_score + 25 return adjusted * 2 def main() -> int64: var private_score: secret int64 = ClientStore.take_share(0, 0) var eligibility = normalize_score(private_score) return eligibility.reveal() ``` For arithmetic, use normal operators when they fit the value shape: ```stoffel theme={null} def compute(a: secret int64, b: secret int64) -> int64: var total = a + b var delta = a - b var product = total * delta var ratio = product / 2 return ratio.reveal() ``` The same operator style works for secret booleans and fixed-point values where the operation is supported: ```stoffel theme={null} def gate(a: secret bool, b: secret bool) -> secret bool: var not_a: secret bool = 1 - a return not_a * b def half(value: secret fix64) -> secret fix64: return value / 2.0 ``` You can still call share operations as methods or functions when you want a specific builtin: ```stoffel theme={null} def compute(a: Share, b: Share) -> int64: var sum = Share.add(a, b) var scaled = sum.mul_scalar(2) return scaled.open() ``` ## ClientStore inputs `ClientStore` reads external client input slots during local or network MPC execution. ```stoffel theme={null} def main() -> int64: var share = ClientStore.take_share(0, 0) return share.open() ``` Run locally with a client-slot input: ```bash theme={null} stoffel run --client-input 0=42 --parties 5 --threshold 1 ``` With the Rust SDK, pass the same slot input using `.with_client_input(0, &[42_i64])`. ## Runtime metadata ```stoffel theme={null} def main() -> int64: var score: int64 = Mpc.party_id() score = score + Mpc.n_parties() score = score + Mpc.threshold() if Mpc.has_capability("multiplication"): score = score + 100 discard Mpc.protocol_name() discard Mpc.curve() discard Mpc.field() discard Mpc.instance_id() return score ``` ## Runnable examples for this page Use the [runnable examples guide](./examples) for a guided path through the examples. For the syntax on this page, these examples are the most direct references: * [`local_control_flow`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_control_flow) for functions, loops, ranges, branching, and arithmetic. * [`local_collections`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_collections) and [`local_nested_generics`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/local_nested_generics) for lists and nested generic list shapes. * [`mpc_share_arithmetic`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_share_arithmetic) and [`mpc_boolean_circuit`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_boolean_circuit) for share arithmetic and `secret bool` values. * [`mpc_client_private_score`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_private_score) and [`mpc_client_federated_average`](https://github.com/Stoffel-Labs/stoffel/tree/main/crates/stoffel-lang/examples/mpc_client_federated_average) for `ClientStore` input slots and client-output patterns. ## Build and run ```bash theme={null} stoffel check stoffel build stoffel run --client-input 0=42 --parties 5 --threshold 1 stoffel compile --disassemble target/debug/.stflb ``` ## See also * [StoffelLang Overview](./overview) * [Runnable Examples](./examples) * [Compilation](./compilation) * [Built-in Functions](../stoffel-vm/builtins) # Built-in Functions Source: https://docs.stoffelmpc.com/stoffel-vm/builtins Current Stoffel VM standard and MPC builtin functions, including Share, ClientStore, Mpc, MpcOutput, storage, and protocol helper modules. Stoffel VM registers standard runtime builtins and MPC-focused module-style builtins. In normal application code, prefer StoffelLang method/operator syntax where available; this page names the VM functions those features lower to. ## General runtime builtins The standard library includes object, array/list, closure, local storage, formatting, assertion, and output helpers. | Builtin group | Names | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Objects and arrays | `create_object`, `create_array`, `get_field`, `get_or_create_array_field`, `set_field`, `array_length`, `array_push`, `array_concat`, `array_repeat`, `array_equals` | | List-style aliases | `append`, `extend`, `copy`, `count`, `index`, `pop`, `remove`, `insert`, `clear`, `reverse`, `sort`, `delete`, `len`, `range` | | Closures/upvalues | `create_closure`, `call_closure`, `get_upvalue`, `set_upvalue` | | Runtime helpers | `print`, `type`, `to_string`, `slice`, `contains`, `assert` | | Local storage | `LocalStorage.store`, `LocalStorage.load`, `LocalStorage.retrieve`, `LocalStorage.delete`, `LocalStorage.exists` | `print` is variadic: it formats arguments, joins them with spaces, and writes one line through the VM's configured output sink. In StoffelLang, prefer method syntax where the language exposes it: ```stoffel theme={null} def main() -> int64: var values: list[int64] = [] values.append(10) values.append(20) return values.len() ``` ## Share builtins `Share` values represent MPC secret shares. The VM exposes these canonical share operations: ```text theme={null} Share.from_clear Share.from_clear_int Share.from_clear_uint Share.from_clear_fixed Share.add Share.sub Share.neg Share.add_scalar Share.mul_scalar Share.mul Share.batch_mul Share.open Share.batch_open Share.send_to_client Share.interpolate_local Share.get_type Share.get_party_id Share.open_exp Share.random Share.random_field Share.random_int Share.get_commitment Share.commitment_count Share.has_commitments Share.mul_field Share.add_field Share.retag Share.open_field Share.open_exp_custom ``` StoffelLang also supports typed secret values and operators. Prefer this shape in app examples when the scalar type is known: ```stoffel theme={null} def compute(a: secret int64, b: secret int64) -> int64: var sum: secret int64 = a + b var product: secret int64 = sum * 2 return product.reveal() ``` Method/function forms remain useful when you want to call a specific builtin directly: ```stoffel theme={null} def compute(a: Share, b: Share) -> int64: var sum = Share.add(a, b) var scaled = sum.mul_scalar(2) return scaled.open() ``` Opening or revealing a share reconstructs a clear value. Do this only at the point where the program intentionally discloses a result. ## Batch operations Use batch operations when a program naturally computes or returns several shares. ```stoffel theme={null} def main() -> list[int64]: var left = Share.from_clear(40) var right = Share.from_clear(2) var values: list[Share] = [left, right, left.add(right)] return Share.batch_open(values) ``` `Share.batch_mul` is the VM builtin for batched share multiplication; use it when the algorithm already has aligned share arrays and should consume MPC multiplication material in batch form. ## ClientStore builtins `ClientStore` bridges external client input material into a Stoffel program. VM-level client slots are ordinal positions in the configured/sorted client roster, not necessarily raw network client IDs. | Builtin | Purpose | | -------------------------------------------------------- | ------------------------------------------------ | | `ClientStore.get_number_clients()` | Total known client slots. | | `ClientStore.get_number_input_clients()` | Number of clients with input material. | | `ClientStore.get_number_output_clients()` | Number of output-capable client slots. | | `ClientStore.take_share(client_slot, input_index)` | Load a default integer share from a client slot. | | `ClientStore.take_share_bool(client_slot, input_index)` | Load a one-bit boolean share from a client slot. | | `ClientStore.take_share_fixed(client_slot, input_index)` | Load a fixed-point share from a client slot. | Example: ```stoffel theme={null} def main() -> int64: var client_count = ClientStore.get_number_clients() var share: secret int64 = ClientStore.take_share(0, 0) return share.reveal() + client_count ``` CLI local execution: ```bash theme={null} stoffel run --client-input 0=42 --parties 5 --threshold 1 ``` Rust SDK local execution: ```rust theme={null} use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; # Ok(()) # } ``` ## Mpc builtins `Mpc` exposes runtime metadata, readiness, capabilities, and public randomness helpers. ```text theme={null} Mpc.party_id Mpc.n_parties Mpc.threshold Mpc.is_ready Mpc.instance_id Mpc.protocol_name Mpc.curve Mpc.field Mpc.has_capability Mpc.capabilities Mpc.rand Mpc.rand_int ``` `Mpc.rand` and `Mpc.rand_int` produce local public randomness. For jointly generated secret-shared randomness, use `Share.random` or `Share.random_int`. Capability names include: ```text theme={null} multiplication elliptic-curves client-input consensus open-in-exponent reservation client-output randomness field-open preprocessing-persistence ``` `Mpc.has_capability(...)` accepts common aliases such as `mul`, `rbc`, `open-exp`, and `preproc-store`. Example: ```stoffel theme={null} def main() -> int64: var score: int64 = Mpc.party_id() score = score + Mpc.n_parties() score = score + Mpc.threshold() if Mpc.is_ready(): score = score + 10 if Mpc.has_capability("multiplication"): score = score + 100 return score ``` ## MpcOutput builtins Use `MpcOutput.send_to_client` when a program should deliver share outputs to client slots through the coordinator/output path. It accepts a client slot and either a single share or a non-empty homogeneous array/list of shares: ```stoffel theme={null} def main() -> int64: var client_count = ClientStore.get_number_clients() var result: secret int64 = ClientStore.take_share(0, 0) + 5 if Mpc.has_capability("client-output"): MpcOutput.send_to_client(0, [result]) return client_count ``` `Share.send_to_client(client_slot)` is also available for single-share output flows. ## Lower-level protocol helper modules The VM also registers module-style helpers used by advanced protocol and cryptographic examples: | Module | Purpose | | ---------- | ----------------------------------------------------------------------------------------------- | | `Bytes.*` | Byte-array construction, concatenation, conversion, slicing, and length helpers. | | `Crypto.*` | Hashing and signature-related helper functions used by protocol examples. | | `Field.*` | Field-oriented helper functions. | | `Rbc.*` | Reliable broadcast helpers. | | `Avss.*` | AVSS share inspection helpers for commitments and key names. See [AVSS](../mpc-protocols/avss). | Treat these as lower-level integration surfaces. Prefer checked examples in `crates/stoffel-lang/examples/` before documenting production-facing patterns around them. Current AVSS helper names: ```text theme={null} Avss.get_commitment Avss.get_key_name Avss.commitment_count Avss.is_avss_share ``` ## See also * [Virtual Machine Overview](./overview) * [StoffelLang Overview](../stoffel-lang/overview) * [Rust SDK Examples](../rust-sdk/examples) * [Stoffel VM Implementation](./implementation) # Stoffel VM Implementation Source: https://docs.stoffelmpc.com/stoffel-vm/implementation Architecture notes for Stoffel VM runtime state, bytecode loading, execution, table memory, hooks, and MPC effects. Stoffel VM is the register-based runtime used by the CLI, StoffelLang compiler, Rust SDK, and lower-level integration surfaces. It is implemented primarily by two crates in the `stoffel` repository: | Crate | Purpose | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `crates/stoffel-vm-types` | Shared compiler/runtime data model: values, instructions, registers, activations, functions, and `.stflb` bytecode. | | `crates/stoffel-vm` | Runtime execution engine, standard library, MPC builtins, async MPC effect scheduling, storage, networking internals, and FFI. | Application docs should start with the CLI and Rust SDK. This page is for understanding the implementation boundary beneath those surfaces. ## Execution model Stoffel VM execution model showing bytecode loading, instruction dispatch, clear and secret register spaces, runtime stores, builtins, and MPC hooks. The runtime executes lowered/resolved instructions, not raw source text. Symbolic `Instruction` values are useful at compiler/direct-construction boundaries; `ResolvedInstruction` values and packed runtime instructions are used for efficient execution. ## VirtualMachine and VMState `VirtualMachine` is a public wrapper around `VMState`. `VirtualMachine::new()` uses the VM builder defaults: * registers the standard library; * registers MPC builtins; * uses the default register layout; * uses in-memory table storage; * uses stdout as the output sink; * starts without a configured MPC engine or local storage backend. `VirtualMachine::without_builtins()` creates an empty VM for tests or custom embedding. `VMState` owns the runtime state: | Component | Role | | ---------------------- | ----------------------------------------------------------- | | Program registry | VM and foreign function definitions. | | Activation stack | Current call frames and return flow. | | Runtime function cache | Lowered instruction data per active frame. | | Register layout | Maps absolute register operands to clear or secret banks. | | Table memory | Object and array storage backend. | | Foreign object store | Rust objects exposed to VM code. | | Hook manager | Optional execution/debug hooks. | | MPC runtime | Optional engine metadata and online operation handle. | | Output sink | Destination used by `print`. | | Local storage | Optional app-provided storage backend for `LocalStorage.*`. | When the runtime needs to execute multiple functions or MPC tasks concurrently, it can clone an empty execution state that shares immutable program metadata while getting independent activation/table state. Configured local storage remains shared. ## Registers and frames The bytecode ABI uses absolute register indices. The default layout treats `r0..r15` as clear registers and `r16..` as secret registers. `r0` is the return register. Each function has a frame register count. Registration normalizes that count so the frame is wide enough for: * at least the return register; * all parameters; * every referenced clear or secret register. A call frame stores: * local variables; * the register file; * captured upvalues; * a volatile argument stack; * a stable spill area; * compare flag; * instruction pointer; * optional closure metadata. The distinction between argument stack and spill area matters: * `PUSHARG` pushes onto the volatile argument stack for calls; * `LD` reads from that argument stack; * `STS` / `LDS` access a separate per-frame spill area used by the register allocator. ## Clear and secret bank transitions The register file stores clear and secret banks separately. The layout maps absolute bytecode registers into a bank and bank-local address. A write or move across banks may have MPC meaning: | Transition | Runtime behavior | | --------------- | ------------------------------------------------------- | | clear → clear | normal value copy | | secret → secret | share/value copy | | clear → secret | clear value is represented as a share value when needed | | secret → clear | reveal/open operation; may yield an async MPC effect | Pending reveals are stored as register-slot state until the online MPC operation completes. They are not ordinary `Value` variants. ## Values and table memory The VM value model includes scalar values, typed table handles, closures, foreign object handles, unit, and share values. Object and array storage is abstracted behind `TableMemory`. The default backend is an in-memory `ObjectStore`, but the trait boundary is designed so future backends, including access-tracking or ORAM-like storage, can preserve read/write metadata. For this reason, execution-path table reads go through mutable table-memory APIs even when a read looks logically immutable. Arrays are 0-indexed. The default array implementation uses dense inline storage for small numeric indices plus an extra field map for large or non-numeric keys, with a cached length hint. ## Functions and calls A `VMFunction` carries: * name; * parameter names; * upvalue names; * optional parent function; * register count; * labels; * symbolic instructions, or resolved instructions with constants and call targets. During registration/loading, the VM validates labels and registers, resolves control-flow targets, interns constants, and resolves call targets. Call-target lookup is cached inside VM state to avoid repeated function-name lookups on hot call paths. VM calls and foreign-function calls share the same call machinery. Method-style calls are resolved against canonical builtin names and receiver types before dispatch. ## Hooks and debugging The VM has an optional hook system for debugging and tooling. When no hooks are enabled, the runtime takes a fast path and does not build hook snapshots. Hook events can cover: * before/after instruction execution; * register reads/writes with absolute and bank-local register information; * variable and upvalue access; * object and array field access; * function calls; * closure creation; * stack push/pop. Use this hook layer for runtime instrumentation instead of adding logging to hot execution paths. ## Async MPC effects The VM can execute ordinary local instructions synchronously. When execution reaches work that needs an MPC engine, async execution yields a typed effect and resumes after the engine returns a result. Examples of operations that can yield effects: * client input sharing; * secret multiplication; * secret boolean bit operations; * opening/revealing a share; * `Share.batch_open`; * `Share.random` / `Share.random_int`; * `MpcOutput.send_to_client`; * lower-level RBC, field-open, and exponent-open operations. The effect scheduler runs local instructions in bounded slices so non-MPC work does not block the async runtime indefinitely. ## Builtins and standard library `VirtualMachine::new()` registers two broad groups: | Group | Examples | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | Standard library | arrays/lists, objects, closures, local storage, `print`, `type`, `slice`, `contains`, `assert`, `ClientStore.*`, `MpcOutput.send_to_client` | | MPC builtins | `Share.*`, `Mpc.*`, `Bytes.*`, `Crypto.*`, `Field.*`, `Rbc.*`, `Avss.*` | `print` writes through the configured VM output sink. `LocalStorage.*` builtins require a local-storage backend to be configured on the VM builder. ## Bytecode and manifests `.stflb` bytecode is defined by `CompiledBinary` in `stoffel-vm-types`. The current format uses magic bytes `STFL` and format version `9`. A compiled binary carries: * a scalar constant pool; * compiled function records; * function type metadata; * client input/output schema metadata; * MPC backend and curve selections; * preprocessing demand estimates. The preprocessing manifest helps local/network MPC runtimes prepare material such as triples, random shares, PRandBits, and PRandInts. A `dynamic` flag indicates that runtime demand may exceed the static estimate. ## Lower-level embedding Direct VM construction is useful for VM tests and bytecode tooling, but most application integrations should use `stoffel build` plus `Stoffel::load_file(...)` from the Rust SDK. ```rust theme={null} 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 = VMFunction::new( "hello".to_string(), vec![], vec![], None, 2, vec![ Instruction::LDI(0, Value::String("Hello from Stoffel VM".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)?; vm.execute("hello")?; Ok(()) } ``` ## See also * [Virtual Machine Overview](./overview) * [Instructions and Types](./instructions) * [Built-in Functions](./builtins) * [Rust SDK Overview](../rust-sdk/overview) # Instructions and Types Source: https://docs.stoffelmpc.com/stoffel-vm/instructions Stoffel VM instruction, register, value, function, and bytecode format reference. Stoffel VM bytecode is register-based. Instructions operate on absolute frame registers, call functions by name before resolution, and are serialized into `.stflb` bytecode by `stoffel-vm-types`. Stoffel VM execution model showing bytecode flowing into the instruction dispatcher, clear and secret register spaces, runtime stores, builtins, and MPC protocol hooks. ## Architecture Overview The VM uses two related instruction forms: | Form | Purpose | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `Instruction` | Symbolic representation with labels, function names, and embedded immediate `Value`s. Used by compiler output and direct VM construction. | | `ResolvedInstruction` | Execution-oriented representation with label targets, constants, and call targets resolved to numeric indices. | During function registration and bytecode loading, labels are resolved, immediates are interned into a per-function constant table, and `CALL("name")` becomes a numeric call-target index plus a call-target name table. The runtime then lowers resolved instructions into packed runtime instructions for dispatch. The packed runtime opcode mapping is an internal optimization and is not the same thing as the serialized opcode values shown below. ## Registers Bytecode register operands are absolute frame indices. The default layout uses register `16` as the secret-register boundary: | Range | Default bank | | --------- | ---------------- | | `r0..r15` | clear registers | | `r16..` | secret registers | Important details: * `r0` is the return register. * The secret boundary is a layout convention, not a hard 32-register maximum. * Each `VMFunction` has a frame register count. Registration normalizes the count so it is large enough for parameters, the return register, and every referenced register. * The register file stores clear and secret banks separately even though bytecode operands are absolute indices. * Moving clear values into secret registers creates or carries share values. Moving secret values back to clear registers becomes a reveal/open operation. * Pending reveals are stored as register-slot state until the async MPC operation completes. ## Frame memory Each activation frame contains: * function name and local variable map; * register file; * captured upvalues; * a volatile argument stack for `PUSHARG`, `LD`, and calls; * a dedicated spill vector for `STS` / `LDS`; * a compare flag; * an instruction pointer; * optional closure metadata. `LD` reads from the current frame's argument stack. Offset `0` resolves to the top argument; negative offsets read below the top. `STS` and `LDS` do not use that argument stack. They access a stable per-frame spill area used by register allocation, so spilled values survive between call argument pushes without being confused with function-call arguments. ## Value types The VM value enum includes: | Variant family | Runtime shape | | --------------------- | ----------------------------------------------------------------------------------- | | Signed integers | `I64`, `I32`, `I16`, `I8` | | Unsigned integers | `U64`, `U32`, `U16`, `U8` | | Other scalars | `Float(F64)`, `Bool`, `String`, `Unit` | | VM-managed references | `Object(ObjectRef)`, `Array(ArrayRef)`, `Foreign(ForeignObjectRef)`, `Closure(...)` | | Shares | `Share(ShareType, ShareData)` | Object, array, and foreign values are typed handles into VM-managed stores. They are not serialized as constants in `.stflb` files. Share type metadata is shape-oriented: ```rust theme={null} ShareType::SecretInt { bit_length } ShareType::SecretUInt { bit_length } ShareType::SecretFixedPoint { precision } ``` Useful defaults: * secret int: 64 bits; * secret bool: one-bit secret int; * fixed point: 64 total bits, 16 fractional bits. ## Instruction set ### Loading and movement | Instruction | Operands | Effect | | ----------- | -------------------- | --------------------------------------------------------------------- | | `NOP` | — | No operation. | | `LD` | `dest, stack_offset` | Load from current frame's argument stack. | | `LDI` | `dest, value` | Load an immediate value; serialized bytecode stores a constant index. | | `MOV` | `dest, src` | Move/copy a register value, including clear/secret bank transitions. | | `PUSHARG` | `src` | Push a register value onto the call argument stack. | | `STS` | `slot, src` | Store a raw value into the frame spill area. | | `LDS` | `dest, slot` | Load a raw value from the frame spill area. | ### Arithmetic and bitwise operations Arithmetic instructions use `dest, left, right` operands: | Instruction | Meaning | | ----------- | --------------------- | | `ADD` | `dest = left + right` | | `SUB` | `dest = left - right` | | `MUL` | `dest = left * right` | | `DIV` | `dest = left / right` | | `MOD` | `dest = left % right` | Bitwise instructions: | Instruction | Meaning | | ----------- | --------------------------------------------------- | | `AND` | `dest = left & right` | | `OR` | `dest = left \| right` | | `XOR` | `dest = left ^ right` | | `NOT` | `dest = !src` / bitwise not depending on value type | | `SHL` | `dest = value << amount_register` | | `SHR` | `dest = value >> amount_register` | `SHL` and `SHR` take the shift amount from a register, not an immediate literal operand. ### Comparisons and jumps `CMP left, right` sets a typed compare flag: * `Less` * `Equal` * `Greater` Conditional jumps read that flag: | Instruction | Jumps when | | ----------- | --------------------------- | | `JMPEQ` | comparison was equal | | `JMPNEQ` | comparison was not equal | | `JMPLT` | comparison was less than | | `JMPGT` | comparison was greater than | `JMP` jumps unconditionally to a label in symbolic form or instruction index in resolved form. ### Calls and returns | Instruction | Purpose | | ----------- | ------------------------------------------------------------------------------ | | `CALL name` | Call a VM or foreign function. Arguments must have been pushed with `PUSHARG`. | | `RET src` | Return a register value to the caller. | At the end of a VM function, the runtime also treats `r0` as the return register for fallthrough-style completion. ## Serialized opcode values The opcode values used by serialized bytecode are: | Hex | Instruction | | ------ | ----------- | | `0x00` | `LD` | | `0x01` | `LDI` | | `0x02` | `MOV` | | `0x03` | `ADD` | | `0x04` | `SUB` | | `0x05` | `MUL` | | `0x06` | `DIV` | | `0x07` | `MOD` | | `0x08` | `AND` | | `0x09` | `OR` | | `0x0A` | `XOR` | | `0x0B` | `NOT` | | `0x0C` | `SHL` | | `0x0D` | `SHR` | | `0x0E` | `JMP` | | `0x0F` | `JMPEQ` | | `0x10` | `JMPNEQ` | | `0x11` | `CALL` | | `0x12` | `RET` | | `0x13` | `PUSHARG` | | `0x14` | `CMP` | | `0x15` | `JMPLT` | | `0x16` | `JMPGT` | | `0x17` | `NOP` | | `0x18` | `LDS` | | `0x19` | `STS` | ## Bytecode format Compiled bytecode is stored in `.stflb` files. The format is defined by `stoffel-vm-types::compiled_binary`. Current format facts: * magic bytes: `STFL`; * format version: `9`; * version 9 added `LDS` / `STS` spill-slot instructions; * generic collection guardrail: 1,000,000 items; * per-function instruction guardrail: 8,000,000 instructions; * string/blob guardrail: 16 MiB. Top-level serialized layout: ```text theme={null} magic bytes: 4 bytes "STFL" format version: u16 constant count: u32 constants: scalar constants only function count: u32 functions: compiled function records client IO manifest versioned fields, when present ``` ### Constants The constant pool supports scalar constants: * `Unit` * signed and unsigned integer values * `Float` * `Bool` * `String` Complex runtime values such as objects, arrays, foreign objects, closures, and shares are created at runtime and are not serialized as constants. ### Function records A compiled function stores: * name; * parameters; * parameter types; * return type; * upvalues; * optional parent function name; * frame register count; * labels; * instructions. Function names, parameter names, upvalues, and labels are length-prefixed strings. Register counts and many counts are bounded integer fields; instruction and label offsets use wider fields for large generated programs. ### Client and MPC manifest The bytecode manifest carries MPC-facing metadata used by the CLI and SDK: * selected MPC backend, such as HoneyBadger or AVSS; * selected curve/field configuration; * client input/output schemas by client slot; * static preprocessing demand estimate. The preprocessing estimate includes counts for Beaver triples, random shares, PRandBits, PRandInts, and a `dynamic` flag for cases where runtime demand may exceed the static estimate. ## See also * [Virtual Machine Overview](./overview) * [Stoffel VM Implementation](./implementation) * [Built-in Functions](./builtins) # Virtual Machine Overview Source: https://docs.stoffelmpc.com/stoffel-vm/overview How Stoffel VM executes bytecode, manages clear and secret registers, and connects StoffelLang programs to local or network MPC runtimes. Stoffel VM is the runtime for compiled Stoffel programs. StoffelLang source compiles to `.stflb` bytecode, and the CLI or Rust SDK loads that bytecode into the VM for clear checks, local MPC testing, or configured network execution. Most application developers should use the CLI and Rust SDK. The VM pages are for understanding what the compiler emits, what the runtime executes, and where MPC-specific behavior enters the system. ## Where the VM fits ```text theme={null} StoffelLang source (.stfl) ↓ stoffel-lang compiler Stoffel bytecode (.stflb) ↓ stoffel-vm-types loader CompiledBinary + VMFunction metadata ↓ stoffel-vm runtime clear VM execution, local MPC testing, or network MPC execution ``` The VM is split across two crates: | Crate | Role | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `stoffel-vm-types` | Shared instruction, value, register, function, activation, and `.stflb` bytecode types. Used by the compiler and runtime. | | `stoffel-vm` | Runtime execution engine, standard library, MPC builtins, async MPC effects, local/network MPC runner internals, storage, and FFI surfaces. | ## Runtime shape The public `VirtualMachine` wraps an internal `VMState`. `VirtualMachine::new()` registers the standard library and MPC builtins by default; `VirtualMachine::without_builtins()` is available for low-level tests and custom embedding. At runtime, VM state contains: * a program/function registry; * an activation stack of call frames; * per-frame register files; * separate clear and secret register banks; * a volatile argument stack for calls; * a stable per-frame spill area for `LDS` / `STS`; * object/array table memory; * foreign-object storage; * optional hooks; * optional MPC runtime metadata and engine handles; * optional local storage; * an output sink used by `print`. ## Registers and secrecy Bytecode operands use absolute frame register indices. The default ABI boundary is register `16`: * registers below `16` are clear registers; * registers `16` and above are secret registers; * `r0` is the return register. This is a boundary, not a fixed 32-register limit. Each `VMFunction` declares or derives the frame register count it needs. The function registration path normalizes that count against parameter count, the return-register ABI, and the highest referenced register. Writes across the clear/secret boundary are meaningful: * clear-to-secret writes create or move share values into the secret bank; * secret-to-clear moves become reveal/open operations; * pending reveals are tracked as register-slot state, not as ordinary `Value` variants. ## Values The VM value model includes scalar values, table references, closures, foreign objects, unit, and share values. | Family | Examples | | --------------------- | ----------------------------------------------------------------------------------- | | Signed integers | `I64`, `I32`, `I16`, `I8` | | Unsigned integers | `U64`, `U32`, `U16`, `U8` | | Other scalars | `Float(F64)`, `Bool`, `String`, `Unit` | | VM-managed references | `Object(ObjectRef)`, `Array(ArrayRef)`, `Foreign(ForeignObjectRef)`, `Closure(...)` | | MPC values | `Share(ShareType, ShareData)` | `Float(F64)` is a VM floating-point value. Secret fixed-point values are represented through `ShareType::SecretFixedPoint`, not by storing public floats as fixed-point integers. Share metadata is typed by shape: * `SecretInt { bit_length }` * `SecretUInt { bit_length }` * `SecretFixedPoint { precision }` `ShareData` can be opaque serialized share data or Feldman share data with commitments, depending on the MPC backend and operation. ## Instructions and function execution The VM has two instruction representations: * symbolic `Instruction` values, which use labels, function names, and immediate `Value`s; * resolved/lowered instructions, which use numeric instruction targets, constant indices, and call target indices for execution. The current instruction set covers: * `NOP`, `LD`, `LDI`, `MOV`; * arithmetic: `ADD`, `SUB`, `MUL`, `DIV`, `MOD`; * bitwise: `AND`, `OR`, `XOR`, `NOT`, `SHL`, `SHR`; * control flow: `CMP`, `JMP`, `JMPEQ`, `JMPNEQ`, `JMPLT`, `JMPGT`; * calls: `PUSHARG`, `CALL`, `RET`; * spill slots: `LDS`, `STS`. Synchronous execution runs local VM work until a function returns or errors. Async execution can run local instructions in slices and yield typed MPC effects when an operation requires an MPC engine, such as input sharing, multiplication, opening, randomness, or client output delivery. ## Builtins and MPC boundary The standard library provides general-purpose runtime functions for arrays, objects, strings, closures, local storage, assertions, printing, and ClientStore input access. MPC-focused builtins expose module-style APIs: * `Share.*` for share construction, arithmetic, random shares, opening, batching, commitments, and client output helpers; * `ClientStore.*` for client-provided private inputs; * `Mpc.*` for party/runtime metadata and capability checks; * `MpcOutput.*` for output delivery to client slots; * lower-level `Bytes.*`, `Crypto.*`, `Field.*`, `Rbc.*`, and `Avss.*` helpers for advanced protocol and cryptographic workflows. For app-facing docs, prefer StoffelLang syntax and SDK/CLI workflows over direct VM internals. For example, write `var total: secret int64 = a + b` in StoffelLang and let the compiler emit the corresponding VM operations. ## See also * [Instructions and Types](./instructions) * [VM Implementation](./implementation) * [VM Usage](./usage) * [Built-in Functions](./builtins) # VM Usage Source: https://docs.stoffelmpc.com/stoffel-vm/usage Run Stoffel VM bytecode through the CLI and Rust SDK, inspect generated artifacts, and know when to avoid low-level VM internals. Most developers should use `stoffel run`, `stoffel dev`, or the Rust SDK. These surfaces compile/load `.stflb` bytecode and run it through clear checks, local MPC testing, or configured network execution. Use direct `stoffel-vm` APIs only for VM tests, bytecode tooling, custom embedding, or runtime instrumentation. ## Build bytecode For projects, prefer `stoffel build`: ```bash theme={null} stoffel check stoffel build stoffel build --release ``` `stoffel build` reads `Stoffel.toml` and writes `.stflb` artifacts under `target/debug/` or `target/release/`. For one selected source file, use `stoffel compile` with an explicit output path: ```bash theme={null} stoffel compile src/main.stfl --output target/debug/main.stflb stoffel compile src/main.stfl -O3 --output target/release/main.stflb ``` ## Inspect bytecode ```bash theme={null} stoffel compile --disassemble target/debug/main.stflb stoffel run --program-info stoffel check --print-ir ``` Use these commands to inspect function metadata, generated instructions, bytecode shape, and runtime program information before debugging low-level behavior. ## Run through the CLI ```bash theme={null} stoffel run stoffel run target/debug/main.stflb --entry main stoffel run src/main.stfl --input a=40 --input b=2 ``` `stoffel run` uses the local MPC test network unless you pass `--network` and `--config` for a deployed network configuration. For `ClientStore` programs, provide client-slot inputs: ```bash theme={null} stoffel run \ --client-input 0=42 \ --expected-output-clients 1 \ --parties 5 \ --threshold 1 ``` The first number in `--client-input 0=42` is the client slot. Repeating the same slot appends inputs for that client slot in order. ## Watch mode ```bash theme={null} stoffel dev --client-input 0=42 --parties 5 --threshold 1 stoffel dev --once --client-input 0=42 --parties 5 --threshold 1 ``` Use `--once` for CI/scripts. Omit it while editing to rebuild and rerun when files change. ## Run through the Rust SDK Clear execution is useful for fast logic checks without MPC networking: ```rust theme={null} use stoffel::prelude::*; fn main() -> stoffel::Result<()> { let result = Stoffel::compile("def main(a: int64, b: int64) -> int64:\n return a + b")? .with_inputs(&[("a", 40_i64), ("b", 2_i64)]) .execute_clear()?; println!("Result: {}", result[0]); Ok(()) } ``` For app-shaped local MPC testing, load the bytecode that the CLI built: ```rust theme={null} use stoffel::prelude::*; # async fn example() -> stoffel::Result<()> { let result = Stoffel::load_file("target/debug/hello-mpc.stflb")? .parties(5) .threshold(1) .with_client_input(0, &[42_i64]) .execute_local() .await?; # Ok(()) # } ``` `.execute_local().await?` runs local MPC testing by spawning several MPC nodes/processes on your machine. ## When to use direct VM APIs Use direct `VirtualMachine` / `VMFunction` APIs when you are: * writing VM unit tests; * building bytecode-generation tooling; * testing the instruction set directly; * experimenting with hooks or custom output sinks; * integrating a custom table-memory or local-storage backend. Do not use direct VM APIs as the first path for application docs. For applications, build bytecode with the CLI and load it through the Rust SDK. ## Debugging checklist * Use `stoffel check --print-ir` before bytecode generation. * Use `stoffel compile --disassemble ...` to inspect instructions. * Use `stoffel run --program-info` to inspect loaded functions and metadata. * For MPC programs, confirm `parties`, `threshold`, client inputs, and `--expected-output-clients` match the program's `ClientStore` and output behavior. * Use VM hooks or direct VM construction only when CLI/SDK-level inspection is not enough. ## See also * [Virtual Machine Overview](./overview) * [Instructions and Types](./instructions) * [CLI Overview](../cli/overview) * [Basic Usage](../getting-started/basic-usage) * [Rust SDK Examples](../rust-sdk/examples) # N-party Battleship Hidden State Source: https://docs.stoffelmpc.com/tutorials/n-party-battleship A focused tutorial for learning private state transitions and recipient-specific reveal policies. The N-party Battleship tutorial is a focused mental-model tutorial. It teaches hidden state: an app can update private state and reveal only the facts a product policy allows, without giving the backend a full plaintext god view. Runnable source: * [N-party Battleship tutorial](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/03-n-party-battleship) * [Full tutorial repository](https://github.com/Stoffel-Labs/Stoffel-tutorials) ## What this teaches Battleship is useful because the privacy failure is obvious. A hidden-board game should not require the service, spectators, support tools, or analytics jobs to see every unshot ship location. The tutorial isolates this product shape: ```text theme={null} private state + action -> private next state + authorized view ``` Use it after the [Rust SDK quickstart](./rust-sdk-quickstart) or the [private matchmaking app](./private-matchmaking) when you want to understand private state transitions in isolation. ## Privacy boundary | Fact | Boundary decision | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | Player list and turn order | Public liveness state. The app needs it to coordinate play. | | Target board cells | Private state. Do not expose unshot locations to the attacker, bystanders, operators, support tools, or analytics jobs. | | Hit mask and remaining health | Private intermediate state unless a reveal policy names a recipient. | | Shot coordinate | Policy-dependent. Public combat log, private result, and delayed reveal are different products. | | Hit/miss/sunk result | Authorized output according to the chosen reveal policy. | | Final winner | Authorized output at game end. | The tutorial’s core lesson is recipient specificity. “Reveal hit/miss” is incomplete until the product says who receives it and when. ## Reveal policies The tutorial compares three policies: 1. Public combat log * Everyone sees the coordinate and result. * The board still stays hidden unless the game explicitly allows review. 2. Private result * Attacker and target see the detailed hit/miss result. * Bystanders may see only that a turn happened. 3. Delayed reveal * Details stay hidden during live play. * Selected logs may open after elimination or game end. None of these is universally correct. The point is that the policy is explicit before the app is implemented. ## Code map ```text theme={null} tutorials/03-n-party-battleship/ normal/ trusted-referee Battleship private/ reveal-policy model src/mpc_helpers.stfl secret-bit helpers and oblivious lookup src/client.rs attacker and target-board client payloads src/bin/battleship_client.rs src/app.rs Rust SDK game-service boundary src/main.rs runnable sample app src/main.stfl private state transition walkthrough tests/ mental-model.md ``` Read the files in this order: 1. `normal/battleship.rs`: see the omniscient-referee backend. 2. `private/battleship.rs`: see recipient-specific authorized views. 3. `src/client.rs`: see the attacker and target-board payloads. 4. `src/app.rs`: see the Rust service generate the local run, attach inputs, and decode the shot result. 5. `src/main.stfl`: see oblivious lookup, private hit-mask update, private remaining-health update, and opened output fields. 6. `tests/`: see reveal-policy behavior and hidden-state expectations. ## Run it From the tutorial repository root: ```bash theme={null} cargo test -p n-party-battleship-tutorial cargo run -p n-party-battleship-tutorial --bin battleship_client -- attacker alice 2 cargo run -p n-party-battleship-tutorial ``` Then run the Stoffel project directly: ```bash theme={null} cd tutorials/03-n-party-battleship stoffel status --verbose stoffel check stoffel build stoffel run \ --client-input 0=2 \ --client-input 1=0,1,1,0,0,0,0,0,2 \ --timeout-secs 600 ``` The direct CLI run returns the opened shot outcome encoded by the tutorial. ## What not to copy Do not copy Battleship as a claim that every game needs MPC. Copy the hidden-state design habit: 1. Separate public liveness state from private app state. 2. Define the recipient-specific view before coding. 3. Update private state without turning it into a backend-readable board. 4. Open only the facts named by the reveal policy. 5. Treat logs, support tools, analytics, and spectators as product surfaces with explicit access rules. ## Return to the app-shaped path After this focused tutorial, return to [Build a private matchmaking service](./private-matchmaking) to see how private computation fits into a richer Rust service, or use the [Rust SDK quickstart](./rust-sdk-quickstart) for the smallest app-shaped Rust service boundary. Use [Private lottery reveal boundary](./private-lottery) for the smaller “one opened result” mental model. # Tutorial overview Source: https://docs.stoffelmpc.com/tutorials/overview Start with the Rust SDK quickstart, then build a private matchmaking service and focused privacy-pattern tutorials. Start with the Rust SDK quickstart if you want the smallest app-shaped project. Then move to the private matchmaking tutorial for the full Rust service boundary, and use the focused tutorials to isolate the privacy patterns you will reuse in your own app: reveal boundaries and hidden state. ## Start here Build a private approval tally that opens only an aggregate yes-vote count, then maps it into a normal Rust launch decision. Compute matches from private feature and preference vectors without exposing raw vectors, scores, rankings, or rejection paths. This is the best next step if you want to see the full Rust service boundary around a Stoffel computation. ## Deepen the mental model Reveal the winner without exposing eligibility bits, ticket values, losing order, or comparison trace. Update hidden state while revealing only the shot result each recipient is allowed to see. Decide what is private, public, opened, routed, or never revealed before adapting the tutorial pattern. ## Run the source The runnable projects live in the [Stoffel tutorials repository](https://github.com/Stoffel-Labs/Stoffel-tutorials). From the repository root: ```bash theme={null} cargo test ``` Then run the quickstart or matching tutorial directly: ```bash theme={null} cd tutorials/00-rust-sdk-quickstart stoffel status --verbose stoffel check stoffel build stoffel run --client-input 0=1 --client-input 1=0 --client-input 2=1 --timeout-secs 180 ``` Each tutorial README includes its exact `cargo run`, `stoffel check`, `stoffel build`, and `stoffel run` commands. ## What to copy into your app Copy the boundary pattern, not the sample domain: 1. Keep public product data in ordinary application code. 2. Give each logical client an explicit client slot and private payload type. 3. Put only the private computation boundary in StoffelLang. 4. Attach client inputs with one `with_client_input(slot, values)` call per logical client slot. 5. Validate the client input shape before running local MPC. 6. Open only the authorized output your product needs. 7. Map the opened result back into a typed Rust domain object. ## Next steps # Design the Privacy Boundary Source: https://docs.stoffelmpc.com/tutorials/privacy-boundary Decide which app facts stay private, which facts are public, and which computed outputs Stoffel is allowed to reveal. A Stoffel app starts with a product question, not a protocol question: ```text theme={null} What facts should this app be allowed to reveal, to whom, and when? ``` That answer defines the privacy boundary. The host app can still own routing, accounts, public metadata, UI, persistence, and ordinary business rules. Stoffel owns the privacy-sensitive computation over private inputs and opens only the result the app is allowed to reveal. Use this page while adapting the tutorial path: * [Private matchmaking](./private-matchmaking) gives the full app-shaped pattern. * [Private lottery](./private-lottery) isolates the authorized-output/reveal-boundary pattern. * [N-party Battleship](./n-party-battleship) isolates hidden state and recipient-specific reveal policies. The progression is app-led: see the full Rust service first, then use the focused tutorials to deepen the mental models. ## Core terms | Term | Meaning | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Private input | A value supplied by a client or party for use inside the computation. It should not become ordinary backend data. | | Public input | A value the app can already know, such as participant slots, feature dimensions, topology, or a public threshold. | | Intermediate state | Private values produced while computing: tickets, scores, comparison bits, hidden board cells, rankings, hit masks. | | Authorized output | A computed fact the app is allowed to reveal to a named recipient at a specific time. In code, this is an opened result or client-routed output. | | Reveal boundary | The line between private inputs/intermediate state and the facts the program opens. | | Leakage policy | The explicit list of facts the app accepts will become known. Outputs leak information too; the point is to make that leakage deliberate and minimal. | ## Boundary design checklist Before writing StoffelLang, write down: 1. Which product feature needs private computation? 2. Who owns each private input? 3. Which facts are already public? 4. Which intermediate facts would be harmful in logs, analytics, admin tools, support exports, or another user's view? 5. What exact output does the app need? 6. Which recipient receives that output? 7. What can the recipient infer from the output? 8. Which facts must never be opened by this program? Then encode that boundary in code: * client payload structs own private input values; * `ClientStore.take_share(...)` loads private input slots; * normal Rust code validates public app shape and maps outputs into domain types; * StoffelLang computes over shares; * `.reveal()`, `Share.open(...)`, `MpcOutput.send_to_client(...)`, or `Share.send_to_client(...)` appears only at the intended output boundary. ## Example: private lottery The product needs to announce a winner. It does not need the operator, support team, analytics job, or other participants to see the full draw board. | Fact | Boundary decision | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Participant slots | Public. The app needs them to map winner index to display name. | | Eligibility bit per participant | Private input. Each participant may know their own value; the operator and other participants do not need the full eligibility board. | | MPC-generated random tickets | Private intermediate state. Do not open. | | Ticket comparison trace | Private intermediate state. Do not open. | | Winner index | Authorized output. Open it so the app can announce the winner. | The private computation can reveal the winner without giving the provider a board it can rerun, tune, export, or use to rank non-winners. ## Example: private matchmaking The product needs to route matches. It does not need the platform to learn every raw vector, score, ranking, and rejection path. | Fact | Boundary decision | | ----------------------------------- | -------------------------------------------------------------------------- | | Public profiles and participant set | Public app data. | | Feature and preference vectors | Private inputs owned by daters. | | Compatibility scores | Private intermediate state. | | Derived rankings | Private intermediate state. | | Proposal/rejection path | Private intermediate state. | | Final match | Authorized output, routed to named recipients according to product policy. | The final match still leaks something: a user learns who they matched with. The privacy boundary prevents that necessary output from expanding into a score table or preference dossier. ## Example: private state transition In a Battleship-style game, the app needs to advance one shot. It does not need a global plaintext god view of all hidden boards. | Fact | Boundary decision | | ----------------------------- | ----------------------------------------------------------------- | | Turn order and player list | Public liveness state. | | Target board cells | Private input/state. | | Hit mask and remaining health | Private intermediate state. | | Shot coordinate | Policy-dependent: public, private to attacker/target, or delayed. | | Hit/miss/sunk result | Authorized output according to the chosen reveal policy. | Recipient-specific rules matter. A public combat log, private result, and delayed reveal policy are different products with different leakage. ## Common mistakes * Opening helper values because they are convenient to debug. * Returning a whole board, ranking table, or score vector when the product needs one decision. * Treating encrypted storage as enough when the backend still decrypts every input to compute. * Aggregating all private inputs into one synthetic client slot when the product has distinct participants. * Hiding product policy inside MPC when it can safely remain ordinary Rust logic. * Saying “the server learns less” without naming who no longer sees which fact. ## Next steps * [Tutorials](./overview) * [Embed Stoffel in a Rust service](../rust-sdk/app-integration) * [MPC Backends](../mpc-protocols/overview) # Private Lottery Reveal Boundary Source: https://docs.stoffelmpc.com/tutorials/private-lottery A focused tutorial for learning authorized outputs: reveal the winner without exposing the full private draw board. The private lottery tutorial is a focused mental-model tutorial. It teaches the reveal-boundary habit: a private computation can open one authorized result without turning every private input and intermediate value into app data. Runnable source: * [Private lottery tutorial](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/01-private-lottery) * [Full tutorial repository](https://github.com/Stoffel-Labs/Stoffel-tutorials) ## What this teaches A lottery is a small version of a common product shape: ```text theme={null} private eligibility + private draw path -> winner ``` The app still owns signups, deadlines, participant slots, and winner announcement. Stoffel owns the sensitive draw. The program opens only the winner index. Use this after the [Rust SDK quickstart](./rust-sdk-quickstart) or the [private matchmaking app](./private-matchmaking) when you want to isolate one concept: the authorized output is not the whole computation trace. ## Privacy boundary | Fact | Boundary decision | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | Participant slots | Public. The app needs them to map the winner index to a display name. | | Eligibility bit per participant | Private input. Do not expose non-winning eligibility to the operator, other participants, support tools, analytics jobs, or logs. | | MPC-generated random tickets | Private intermediate state. Do not open ticket values or let them become a loser-ranking table. | | Ticket comparison trace | Private intermediate state. Do not reveal how close each participant came to winning. | | Winner index | Authorized output. Open it so the app can announce the winner. | The private computation can reveal the winner without giving the provider a board it can rerun, tune, export, or use to rank non-winners. ## Code map ```text theme={null} tutorials/01-private-lottery/ normal/ trusted-server implementation private/ privacy-boundary model src/client.rs participant client payloads src/bin/participant_client.rs src/app.rs Rust SDK app service src/main.rs runnable sample app src/main.stfl Stoffel draw program tests/ app and privacy-boundary tests mental-model.md ``` Read the files in this order: 1. `normal/lottery.rs`: see what the trusted server can see and control. 2. `private/lottery.rs`: see the authorized output model. 3. `src/client.rs`: see each participant’s private eligibility payload. 4. `src/app.rs`: see the Rust service compile, attach inputs, run local MPC, and map the winner index back to a participant. 5. `src/main.stfl`: see private ticket generation, private comparison, private selection, and the single reveal. ## Run it From the tutorial repository root: ```bash theme={null} cargo test -p private-lottery-tutorial cargo run -p private-lottery-tutorial --bin participant_client -- alice 0 true cargo run -p private-lottery-tutorial ``` Then run the Stoffel project directly: ```bash theme={null} cd tutorials/01-private-lottery stoffel status --verbose stoffel check stoffel build stoffel run \ --client-input 0=1 \ --client-input 1=1 \ --client-input 2=1 \ --timeout-secs 240 ``` The direct CLI run returns the opened winner index for the sample slots. ## What not to copy Do not turn this into a global rule that every app should use lotteries or randomness. Copy the boundary pattern: 1. The product needs one opened decision. 2. The private inputs and intermediate trace are not useful product surfaces. 3. The app names the recipient of the opened result. 4. Everything else stays inside the private computation. ## Return to the app-shaped path After this focused tutorial, return to [Build a private matchmaking service](./private-matchmaking) to see the same reveal-boundary habit inside a richer Rust app, or step back to the [Rust SDK quickstart](./rust-sdk-quickstart) if you want the smallest app-shaped version of the service boundary. Use [N-party Battleship hidden state](./n-party-battleship) when you want the next mental model: private state transitions with recipient-specific views. # Build a Private Matchmaking Service Source: https://docs.stoffelmpc.com/tutorials/private-matchmaking Use Stoffel from a Rust service to compute matches from private preference vectors without exposing raw vectors, scores, rankings, or rejection paths. This is the flagship app-shaped tutorial. Use it after the [Rust SDK quickstart](./rust-sdk-quickstart) when you want to see the same service boundary in a richer application. The tutorial builds a dating-style matcher. Public app logic owns profiles, accounts, cohort selection, and notifications. Stoffel owns the sensitive computation over private feature and preference vectors. The app opens only the match result it is allowed to route. Runnable source: * [Private matchmaking tutorial](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/02-private-matchmaking) * [Full tutorial repository](https://github.com/Stoffel-Labs/Stoffel-tutorials) ## What this teaches A dating app does not only store sensitive data. It computes over it. The platform may show public profiles, photos, prompts, and basic account metadata. Matching often depends on more sensitive context: what someone is looking for, how candidates score, how rankings are derived, and which conflicts or rejections happen along the way. The tutorial teaches the app boundary: ```text theme={null} Public app state: profiles, cohort, client slots, notification flow Private computation: feature vectors, preference vectors, scores, rankings, proposal trace Authorized output: final match routed to named recipients ``` ## Privacy boundary | Fact | Boundary decision | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | Public profiles and participant set | Ordinary app data. The app can use it for discovery, cohort selection, and routing. | | Feature vector per dater | Private input owned by that dater. Do not turn it into a platform label, analytics segment, or support-visible field. | | Preference vector per dater | Private input owned by that dater. Do not expose it to the operator, other users, support tools, analytics jobs, or logs. | | Compatibility scores | Private intermediate state. Do not reveal a score table or desirability ranking. | | Derived rankings | Private intermediate state. Do not reveal rejection reasons or non-match ordering. | | Proposal/rejection path | Private intermediate state. Do not expose which conflicts occurred or why a match changed. | | Final match | Authorized output. Route only to the recipients named by the product policy. | The final match still leaks something. If Alice learns she matched with Emery, Alice learns a fact about the joint process. The point is to keep that necessary output from expanding into raw vectors, score tables, rankings, and rejection traces. ## Code map ```text theme={null} tutorials/02-private-matchmaking/ normal/ clear Rust feature-derived matcher private/ authorized-output model src/private_matching_helpers.stfl src/feature_stable_matching.stfl src/client.rs dater client payloads src/bin/dater_client.rs src/app.rs Rust SDK app service src/main.rs runnable sample app src/main.stfl checked-in CLI walkthrough tests/ mental-model.md ``` Read the files in this order: 1. `normal/`: see what the trusted backend can compute because it sees every vector and ranking. 2. `private/`: see the authorized output model the app should expose. 3. `src/client.rs`: see how each dater owns a client slot and private payload. 4. `src/app.rs`: see the Rust SDK boundary that validates the cohort, attaches client inputs, runs local MPC, and decodes the output. 5. `src/main.stfl`: see the private computation and explicit reveal point. 6. `tests/`: see what the tutorial treats as app behavior versus private intermediate state. ## Run it From the tutorial repository root: ```bash theme={null} cargo test -p private-matchmaking-tutorial cargo run -p private-matchmaking-tutorial --bin dater_client -- alice 0 proposer 1,0 1,0 cargo run -p private-matchmaking-tutorial ``` Then run the Stoffel project directly: ```bash theme={null} cd tutorials/02-private-matchmaking stoffel status --verbose stoffel check stoffel build stoffel run src/main.stfl \ --client-input 0=1,0,1,0 \ --client-input 1=0,1,1,0 \ --client-input 2=1,0,0,1 \ --client-input 3=1,0,0,1 \ --client-input 4=0,1,1,0 \ --client-input 5=1,0,1,0 \ --timeout-secs 600 ``` Expected output: ```text theme={null} 19 ``` In the sample, `19` encodes: ```text theme={null} Alice -> Emery Bob -> Casey River -> Devon ``` ## Mental model links Use the concept tutorials when you hit a specific privacy question: * If you want a smaller example focused only on “open one result, not the whole decision board,” read [Private lottery reveal boundary](./private-lottery). * If you want a focused example of private state transitions and recipient-specific views, read [N-party Battleship hidden state](./n-party-battleship). * If you are adapting this to your own product, use [Design the privacy boundary](./privacy-boundary). ## Adapt the pattern When you adapt the tutorial, keep these invariants: 1. Public profile and discovery data can stay in normal Rust code. 2. Each user owns their private feature or preference payload. 3. The app service validates public shape before running MPC. 4. The StoffelLang program computes only the sensitive matching boundary. 5. Scores, rankings, and rejection paths stay private unless the product explicitly names them as outputs. 6. The final match is routed to named recipients, not treated as a global transcript. You can change the feature model, cohort construction, output routing, and app UI without changing the core design habit: private intermediate facts are not ordinary backend data. ## Next steps * [Design the privacy boundary](./privacy-boundary) * [Embed Stoffel in a Rust service](../rust-sdk/app-integration) * [Run a local Docker MPC network](../deployment/docker-local-network) # Rust SDK Quickstart Source: https://docs.stoffelmpc.com/tutorials/rust-sdk-quickstart Build the smallest app-shaped Rust SDK project: a private approval tally that opens only an aggregate yes-vote count. Use this quickstart when you want the smallest runnable Rust app before the larger tutorial apps. It is shaped like a `stoffel init` project with normal Rust application code around the Stoffel program. The example is a private approval tally for a feature launch. The app owns the proposal, requester, public voter roster, approval threshold, and persistence. Each voter contributes one private yes/no bit through `ClientStore`. Stoffel opens only the aggregate yes-vote count, and the Rust app maps that opened count into a launch decision. Runnable source: * [Rust SDK quickstart](https://github.com/Stoffel-Labs/Stoffel-tutorials/tree/main/tutorials/00-rust-sdk-quickstart) * [Full tutorial repository](https://github.com/Stoffel-Labs/Stoffel-tutorials) ## What this teaches This is the minimal app-shaped SDK path: ```text theme={null} public proposal + public voter roster + private voter bits -> opened yes-vote count -> Rust launch decision ``` Use it to see the SDK handoff without the extra domain logic in matchmaking, lottery, or Battleship. ## Privacy boundary | Fact | Boundary decision | | ----------------------------------------------------- | ------------------------------------------------------------------------------ | | Proposal, requester, voter roster, approval threshold | Public app data. The app can store and display it. | | One approval bit per voter | Private input owned by that voter. Do not store it as ordinary backend data. | | Aggregate yes-vote count | Authorized output. Open it so the app can apply the public approval threshold. | | Final launch decision | Ordinary app result derived from the opened count and public threshold. | The app learns the count it needs to decide whether the threshold passed. It does not need every voter's ballot in the repository, logs, support tools, or analytics jobs. ## Code map ```text theme={null} tutorials/00-rust-sdk-quickstart/ Stoffel.toml Cargo.toml build.rs generated binding setup src/main.stfl private approval tally src/domain.rs public product types src/client.rs voter-owned private payload src/repository.rs public round metadata and opened decisions src/app.rs Rust SDK service boundary src/main.rs runnable sample app src/bin/voter_client.rs src/plaintext.rs trusted-server baseline for tests tests/quickstart.rs ``` Read the files in this order: 1. `src/domain.rs`: see the ordinary proposal, round, voter roster, and decision types. 2. `src/client.rs`: see how each voter owns a client slot and private approval bit. 3. `src/main.stfl`: see the private tally and single reveal point. 4. `src/app.rs`: see the Rust SDK boundary that validates input shape, attaches client inputs, runs local MPC, and persists the opened decision. 5. `tests/quickstart.rs`: see app-layer behavior and the trusted-server baseline. ## Run it From the tutorial repository root: ```bash theme={null} cargo test -p rust-sdk-quickstart cargo run -p rust-sdk-quickstart --bin voter_client -- alice product 0 yes cargo run -p rust-sdk-quickstart cargo run -p rust-sdk-quickstart -- alice:product:0:yes bob:security:1:no cara:infra:2:yes ``` Expected app output: ```text theme={null} proposal feature-flag-private-beta (Enable private beta launch for three design partners) requested by devrel: 2/2 approvals across 3 private voter clients => passed ``` Then run the Stoffel project directly: ```bash theme={null} cd tutorials/00-rust-sdk-quickstart stoffel status --verbose stoffel check stoffel build stoffel run --client-input 0=1 --client-input 1=0 --client-input 2=1 --timeout-secs 180 ``` Expected CLI output: `2` or a result list containing `2`, depending on the installed CLI display format. ## Try a different cohort The Stoffel program uses `ClientStore.get_number_input_clients()`, so the local run can use any contiguous client slots starting at `0`: ```bash theme={null} cargo run -p rust-sdk-quickstart -- alice:product:0:yes bob:security:1:no cara:infra:2:no dan:eng:3:yes stoffel run --client-input 0=1 --client-input 1=0 --client-input 2=0 --client-input 3=1 --timeout-secs 180 ``` Keep the slots contiguous. The tutorial source loads slots `0..n`; a missing slot means the app cannot know which private input belongs to which public voter. ## Next steps After this quickstart, use [Build a private matchmaking service](./private-matchmaking) to see the same SDK boundary inside a richer app-shaped tutorial. Use [Rust SDK App Integration](../rust-sdk/app-integration) when you want the reusable pattern without a tutorial domain.