Introduction

⚠️ Work in Progress Notice

This documentation is actively being developed and is subject to frequent changes. Some sections may be incomplete or pending review.

About Stoffel VM

Stoffel is a framework that enables you to build privacy-first applications using secure Multi-Party Computation (MPC) without needing to be a cryptographer.

Key Features

  • Stoffel VM: virtual machine architecture designed specifically for secure Multi-Party Computation (MPC) applications. It provides a flexible and efficient foundation for executing MPC protocols while maintaining protocol agnosticism.
  • Stoffel compiler: The glue that combines Stoffel Lang and the Stoffel VM
  • Stoffel Lang: A Python-styled DSL that allows you to describe an MPC program naturally
  • Langauge SDKs: A set of SDKs in popular programming languages that allows you to integrate Stoffel directly where you will need it.

Current Status

The project is under active development.

Documentation Structure

The following chapters will guide you through the various aspects of Stoffel:

  • Getting Started
  • Introduction: Provides a more comprehensive overview of Stoffel and Multiparty Computation
  • Architecture: Provides a more comprehensive overview of the architecture of the StoffelVM

This documentation covers the architectural decisions, implementation details, and future development plans for Stoffel VM. Navigate through the sections to learn more about specific aspects of the system.

What is Multi-Party Computation?

Why Stoffel?

Getting Started with Stoffel VM

Introduction

Stoffel VM is a register-based virtual machine designed for both simplicity and power. It's built in Rust and optimized for performance and safety, with special consideration for multiparty computation (MPC) capabilities.

Quick Start

Installation

Currently, Stoffel VM can be used as a library in your Rust projects. Add it to your Cargo.toml:

[dependencies]
stoffel-vm = { git = "https://github.com/Stoffel-Labs/StoffelVM" }

Your First Program

Here's a simple "Hello, World!" program using Stoffel VM:

use stoffel_vm::core_vm::VirtualMachine;
use stoffel_vm::functions::VMFunction;
use stoffel_vm::instructions::Instruction;
use stoffel_vm::core_types::Value;
use std::collections::HashMap;

fn main() -> Result<(), String> {
    // Create a new VM instance
    let vm = VirtualMachine::new();
    
    // Define a hello world function
    let hello_world = VMFunction {
        name: "hello_world".to_string(),
        parameters: vec![],
        upvalues: vec![],
        parent: None,
        register_count: 1,
        instructions: vec![
            // Load string into register 0
            Instruction::LDI(0, Value::String("Hello, World!".to_string())),
            // Push as argument for print
            Instruction::PUSHARG(0),
            // Call print function
            Instruction::CALL("print".to_string()),
            // Return void
            Instruction::RET(0),
        ],
        labels: HashMap::new(),
    };
    
    // Register and execute
    vm.register_function(hello_world);
    vm.execute("hello_world")?;
    
    Ok(())
}

Core Concepts

1. Register-Based Architecture

Stoffel VM uses registers instead of a stack for primary computation. Each function has its own set of registers:

  • Registers are numbered starting from 0
  • Register count is specified per function
  • Values can be moved between registers using MOV

2. Value Types

The VM supports several value types:

  • Int: 64-bit integers
  • Float: Fixed-point numbers
  • Bool: Boolean values
  • String: UTF-8 strings
  • Object: Key-value stores
  • Array: Indexed collections
  • Closure: Function closures
  • Foreign: External Rust objects
  • Unit: Void/null value

3. Instructions

Basic instruction categories:

Memory Operations

#![allow(unused)]
fn main() {
LDI(reg, value)    // Load immediate value
MOV(dest, src)     // Move between registers
PUSHARG(reg)       // Push argument for function call
}

Arithmetic

#![allow(unused)]
fn main() {
ADD(dest, src1, src2)
SUB(dest, src1, src2)
MUL(dest, src1, src2)
DIV(dest, src1, src2)
}

Control Flow

#![allow(unused)]
fn main() {
JMP(label)         // Unconditional jump
JMPEQ(label)       // Jump if equal
CALL(function)     // Call function
RET(reg)           // Return value
}

4. Functions

Functions in Stoffel VM have:

  • A unique name
  • Parameter list
  • Register count
  • Instruction sequence
  • Optional upvalues for closures

Example function definition:

#![allow(unused)]
fn main() {
let function = VMFunction {
    name: "add".to_string(),
    parameters: vec!["a".to_string(), "b".to_string()],
    upvalues: vec![],
    parent: None,
    register_count: 3,
    instructions: vec![
        Instruction::ADD(2, 0, 1),
        Instruction::RET(2),
    ],
    labels: HashMap::new(),
};
}

5. Built-in Functions

Stoffel VM provides several built-in functions:

  • print: Output values
  • create_object: Create new objects
  • create_array: Create arrays
  • get_field/set_field: Object/array manipulation
  • type: Get value type

Debugging

Stoffel VM includes a powerful hook system for debugging:

#![allow(unused)]
fn main() {
vm.register_hook(
    |event| matches!(event, HookEvent::BeforeInstructionExecute(_)),
    move |event, ctx| {
        println!("Executing: {:?}", event);
        Ok(())
    },
    100
);
}

Best Practices

  1. Register Management

    • Keep register count minimal
    • Reuse registers when possible
    • Document register usage
  2. Error Handling

    • Always check function return values
    • Use the hook system for debugging
    • Handle division by zero
  3. Memory Efficiency

    • Release foreign objects when done
    • Clear arrays/objects when no longer needed
    • Be mindful of closure captures
  4. Performance Tips

    • Use immediate values when possible
    • Minimize function calls in loops
    • Prefer register operations over memory

Advanced Features

Closures

Closures capture their environment:

#![allow(unused)]
fn main() {
// Create a counter
let create_counter = VMFunction {
    name: "create_counter".to_string(),
    parameters: vec!["start".to_string()],
    upvalues: vec![],
    instructions: vec![
        // Create closure with upvalue
        Instruction::LDI(1, Value::String("increment".to_string())),
        Instruction::PUSHARG(1),
        Instruction::LDI(2, Value::String("start".to_string())),
        Instruction::PUSHARG(2),
        Instruction::CALL("create_closure".to_string()),
        Instruction::RET(0),
    ],
    // ...
};
}

Foreign Functions

Integrate Rust functions:

#![allow(unused)]
fn main() {
vm.register_foreign_function("double", |ctx| {
    match &ctx.args[0] {
        Value::Int(n) => Ok(Value::Int(n * 2)),
        _ => Err("Expected integer".to_string()),
    }
});
}

Common Patterns

1. Looping

#![allow(unused)]
fn main() {
// Basic loop structure
let mut labels = HashMap::new();
labels.insert("loop_start".to_string(), 1);
labels.insert("loop_end".to_string(), 6);

vec![
    // Initialize counter
    Instruction::LDI(0, Value::Int(0)),
    // Compare
    Instruction::CMP(0, 1),
    Instruction::JMPEQ("loop_end".to_string()),
    // Loop body
    // ...
    Instruction::JMP("loop_start".to_string()),
]
}

2. Conditional Execution

#![allow(unused)]
fn main() {
vec![
    Instruction::CMP(0, 1),
    Instruction::JMPNEQ("else_branch".to_string()),
    // if branch
    // ...
    Instruction::JMP("end_if".to_string()),
    // else branch
    // ...
]
}

Further Reading

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Submit a pull request

Design Rationale

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

Architecture

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.

Technical Overview

Virtual Machine Architecture

  • What type of VM is stoffel
  • Clear vs Secret values

Instruction Set

  • Complete reference of supported VM instructions
  • Opcode specifications and behavior
  • Optimization opportunities

Builtin Types

  • Overview of core data types (numbers, strings, arrays, etc.)
  • Type conversion and manipulation
  • Memory representation and optimization

Activation Records

  • Call stack management and function invocation
  • Local variable scoping and lifetime
  • Optimizing stack frame allocation

VM Functions

  • Virtual machine architecture overview
  • Execution model and stack management
  • Error handling

Closures Overview

  • Lexical scoping and variable capture
  • Implementation details and memory management

Foreign Function Interface

  • Integrating with external libraries and systems
  • Data marshalling and type conversion
  • Performance considerations for FFI calls

Builtin Methods

  • Standard library functions and utilities
  • Common operations for each data type

Runtime Hooks

  • 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.

Sources

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!