Trident: Universal Language for Provable Computation

Design Document — v0.2 Draft February 2026

From single-target ZK language to universal compilation source for all provable virtual machines.


1. Executive Summary

Trident is a minimal, security-first programming language originally targeting Triton VM for zero-knowledge provable computation. This document outlines the design for evolving Trident into a universal source language capable of compiling to any zkVM — including Triton VM, Miden VM, Cairo VM (StarkWare), SP1/RISC Zero (RISC-V zkVMs), and NockVM — while preserving the core properties that make it valuable: bounded execution, cost transparency, and auditability.

1.1 Thesis

Approximately 76% of Trident’s language surface is already portable or trivially abstractable across zkVMs. The remaining ~24% consists of backend extensions — target-specific capabilities that each VM exposes through a uniform extension mechanism. This makes Trident an unusually strong candidate for a universal provable computation language, requiring architectural refactoring rather than language redesign.

1.2 Core Insight

Every zkVM computes over a finite field. Field is the universal primitive of provable computation — the specific prime is an implementation detail of the proof system, not a semantic property of the program. A Trident program that multiplies two field elements and asserts the result means the same thing on every zkVM. The developer reasons about field arithmetic abstractly; the backend implements it concretely.

This is analogous to how int in C means “integer of platform-native width.” You write arithmetic, the compiler picks the encoding. Field in Trident means “element of the target VM’s native field.” Programs should never depend on the specific modulus — and the language design enforces this.

1.3 Architecture

┌───────────────────────────────────────────┐
│         Trident Universal Core            │
│   (types, control flow, modules, field    │
│    arithmetic, I/O, cost transparency)    │
├───────────────────────────────────────────┤
│         Abstraction Layer                 │
│   (hash, memory, stack/register mgmt,    │
│    Merkle ops, cost model, events)        │
├─────────┬─────────┬─────────┬────────────┤
│ Triton  │  Miden  │  Cairo  │  SP1/RZ    │
│ Backend │ Backend │ Backend │  Backend   │
│         │         │         │            │
│  + ext  │  + ext  │  + ext  │  + ext     │
└─────────┴─────────┴─────────┴────────────┘

Each backend implements the abstraction layer for its target VM and may publish backend extensions — additional types, intrinsics, and standard library modules that expose target-specific capabilities. Programs that use extensions are explicitly bound to that backend.

1.4 Design Goals

  1. Write once, prove anywhere. A single Trident program compiles to multiple zkVM targets with target-appropriate optimizations.
  2. Preserve auditability. Direct emission (no IR) for stack-machine targets; minimal IR for register-machine targets.
  3. Cost transparency per target. Every function annotated with proving cost in the target VM’s native cost model.
  4. Backend extensions, not limitations. Target-specific features are capabilities a backend adds to the universal core, not restrictions on portability.
  5. Incremental adoption. Existing Triton-targeting Trident programs continue to compile unchanged.

1.5 Non-Goals

  • General-purpose programming (no strings, no heap, no unbounded execution)
  • Competing with Rust on RISC-V zkVMs for general workloads
  • Supporting non-ZK virtual machines (EVM, WASM, SVM) as primary targets
  • Package registry or ecosystem tooling (premature at this stage)

2. Target zkVM Landscape

2.1 Supported Targets

TargetArchitectureFieldHashProof SystemPriority
Triton VMStack (16-element)Goldilocks (2⁶⁴−2³²+1)Tip5STARKNative
Miden VMStack (16-element)Goldilocks (2⁶⁴−2³²+1)RPOSTARK1
Cairo VMRegister (AP, FP, PC)252-bit primePoseidon/PedersenSTARK2
SP1 / RISC ZeroRegister (RISC-V rv32im)VariousVariousSTARK3
NockVM (Zorp)Combinator reductionArbitrary-precisionTBDSTARK4 (exploratory)

2.2 Architectural Families

Family A: Stack Machines — Triton VM, Miden VM

  • 16-element operational stack
  • Stack manipulation instructions (swap, dup, pop)
  • Direct emission from AST traversal (no IR needed)
  • Trident’s current emission model works with minor adaptation

Family B: Register Machines — Cairo VM, RISC-V zkVMs (SP1, RISC Zero)

  • Named registers or register file
  • Requires register allocation
  • Needs a lightweight IR between type checking and emission

Family C: Reduction Machines — NockVM

  • Binary tree (noun) data model
  • Combinator-based computation
  • Fundamentally different paradigm; long-term exploratory target

3. The Universal Core

These features compile identically to any target with no adaptation. They constitute ~55% of the language surface.

3.1 Type System

Field           → Native field element of the target VM
                  (Goldilocks on Triton/Miden, 252-bit on Cairo, etc.)
                  The universal primitive of provable computation.

Bool            → 0 or 1; native on all targets
U32             → 32-bit unsigned integer; native or emulated everywhere
[T; N]          → Fixed array; flattened to sequential memory on all targets
(T1, T2)        → Tuple; flattened to sequential elements
struct { ... }  → Named product type; compiler-only abstraction

Field is Tier 1 — universally portable. The prime differs per target but the semantics are identical: elements of a finite field with addition, multiplication, and inversion. Programs should never depend on the specific modulus. The only place where field size leaks into program semantics is split(), where the number of U32 limbs depends on field width — handled via the FIELD_LIMBS target constant.

All composite types have compile-time-known widths. The width unit (field elements vs bytes) varies per target, but the width computation algorithm is universal.

3.2 Field Arithmetic

a + b           → field addition (mod p)
a * b           → field multiplication (mod p)
inv(a)          → multiplicative inverse
neg(a)          → additive inverse (p − a)
sub(a, b)       → field subtraction (a + neg(b))
a == b          → field equality

All field operations are universally portable. The instruction encoding differs per backend but the mathematical semantics are identical across all targets.

3.3 Integer Arithmetic

a < b           → u32 comparison
a & b           → bitwise and
a ^ b           → bitwise xor
a /% b          → divmod → (quotient, remainder)
log2(a)         → floor log base 2
pow(base, exp)  → exponentiation
popcount(a)     → Hamming weight (population count)

All U32 operations are universally portable. Every zkVM supports 32-bit integer operations, whether natively (Triton, Miden) or through standard instructions (RISC-V) or range-checked felt decomposition (Cairo).

3.4 Control Flow

if / else                     → Universal branching
for i in 0..N                 → Bounded loop (constant); unrollable everywhere
for i in 0..n bounded MAX     → Bounded loop (runtime variable); universal pattern
match expr { ... }            → Desugared to nested if/else at AST level
assert(condition)             → "Halt if false"; fundamental ZK verification primitive
assert_eq(a, b)               → Sugar for assert(a == b)

Bounded execution is the single most important portability property. Every zkVM requires deterministic trace length. Trident’s mandatory loop bounds, prohibition of recursion, and static memory satisfy this universally.

3.5 Functions and Modules

fn definitions (no recursion)       → Call/return on stack VMs; jump-and-link on register VMs
pub / private visibility            → Compiler-only; never reaches the VM
module / use imports                → Resolved at compile time; emitted as flat code
DAG dependency enforcement          → Compiler-only
const declarations                  → Inlined at compile time; zero runtime cost
Size-generic fn foo<N>()            → Monomorphization; backend never sees generics
#[test] functions                   → Compiler-only
#[cfg(flag)] conditional compilation → Compiler-only

3.6 Expressions

All universally portable:

  • Integer and boolean literals
  • Array, struct, and tuple initialization
  • Field and index access (p.x, arr[i])
  • Variable binding (let / let mut) and assignment
  • Tuple destructuring (let (a, b) = ...)
  • Block expressions

3.7 Project Infrastructure

  • trident.toml project configuration
  • File layout conventions
  • CLI commands: build, check, fmt, test, doc, init, lsp
  • Editor support (Zed, Helix, LSP)

4. The Abstraction Layer

Features that exist on all zkVMs but with different concrete representations. Each needs a thin interface that backends implement. ~21% of the language surface.

4.1 I/O Primitives

Every zkVM distinguishes public input, public output, and private witness. The mechanism differs but the semantic model is identical.

OperationTritonMidenCairoSP1
Read public inputread_io Nadv.pushProgram input segmentsp1_io::read()
Write public outputwrite_io NOutput stackProgram output segmentsp1_io::commit()
Read private witnessdivine NAdvice providerHint blocksp1_io::read() (witness)

User-facing syntax unchanged:

let a: Field = pub_read()       // Read from public input
pub_write(result)               // Write to public output
let s: Field = divine()         // Read from private witness

Backend interface:

trait IOBackend {
    fn emit_pub_read(&mut self, count: usize);
    fn emit_pub_write(&mut self, count: usize);
    fn emit_divine(&mut self, count: usize);
}

4.2 Memory Access

AspectTriton/MidenCairoRISC-V
AddressingWord-addressed (1 field element per cell)Felt-addressed (write-once)Byte-addressed (read/write)
First read (uninitialized)Prover-supplied value (non-deterministic)UndefinedReturns zero
Block read/writeNative instructionsLoop of single readsLoad/store instructions

User-facing syntax unchanged:

ram_write(address, value)
let v: Field = ram_read(address)

Backend interface:

trait MemoryBackend {
    fn emit_read_word(&mut self, addr_on_stack: bool);
    fn emit_write_word(&mut self, addr_on_stack: bool);
    fn emit_read_block(&mut self, count: usize);
    fn emit_write_block(&mut self, count: usize);
    fn is_non_deterministic_on_first_read(&self) -> bool;
    fn is_write_once(&self) -> bool;
}

The compiler emits warnings when targeting write-once memory (Cairo) if a program writes to the same address twice.

4.3 Stack / Register Management

AspectStack VMs (Triton, Miden)Register VMs (Cairo, SP1/RZ)
Variable storageStack positions (16-element limit)Registers (3 on Cairo, 32 on RISC-V)
Spill strategyRAM spill when >16 live variablesRegister spill to stack frame
Operand accessswap N / dup N to bring to topDirect register addressing

Backend interface:

trait AllocationStrategy {
    fn allocate_variable(&mut self, name: &str, width: usize) -> Location;
    fn emit_load(&mut self, loc: &Location);
    fn emit_store(&mut self, loc: &Location);
    fn emit_spill(&mut self, loc: &Location);
    fn max_fast_slots(&self) -> usize;  // 16 for stack VMs, 32 for RISC-V
}

The user never sees the difference. Current Trident’s stack.rs becomes the stack-VM implementation; a new regalloc.rs serves register-machine targets.

4.4 Hash Primitives

Hash functions are the cryptographic backbone of every zkVM but each uses a different one.

VMHash FunctionDigest WidthNative Cost
TritonTip55 field elements1cc + 6 hash rows
MidenRPO (Rescue Prime Optimized)4 field elements~1cc equivalent
CairoPoseidon / PedersenVariesBuiltin coprocessor
SP1SHA-256, Keccak, Poseidon (precompiles)32 bytesPrecompile cost

User-facing syntax unchanged:

let d: Digest = hash(input)
sponge_init()
sponge_absorb(elements)
let squeezed = sponge_squeeze()

Target constants exposed to programs:

DIGEST_WIDTH    → 5 (Triton/Tip5), 4 (Miden/RPO), varies (others)
HASH_RATE       → 10 (Tip5), 8 (RPO), 3 (Poseidon), varies
FIELD_LIMBS     → 2 (Goldilocks), 8 (252-bit Cairo)

Digest type: Defined as [Field; DIGEST_WIDTH] where DIGEST_WIDTH is a compile-time constant set by the target. This preserves Trident’s “what you see is what you prove” philosophy — the width is visible, not hidden behind an opaque type.

Backend interface:

struct HashConfig {
    name: &'static str,      // "tip5", "rpo", "poseidon"
    rate: usize,             // Elements absorbed per round
    digest_width: usize,     // Elements per digest
    is_native: bool,         // Native instruction vs library implementation
}

4.5 Merkle Tree Operations

Merkle verification is algorithmically identical across all zkVMs: iterate from leaf to root, hashing at each level with the sibling. The difference is whether the VM has a native instruction for it.

VMMerkle SupportImplementation
TritonNative merkle_step instructionSingle instruction
MidenNative mtree_get, mtree_setSingle instruction
CairoLibrary code using PoseidonHash loop
SP1/RZLibrary code using precompileHash loop

Abstraction strategy: std.merkle becomes target-polymorphic. On VMs with native Merkle instructions, the body compiles to a single instruction. On others, it compiles to a loop of hash operations with divine() for sibling digests:

pub fn verify(root: Digest, leaf: Digest, index: U32, depth: U32) {
    let mut current = leaf
    let mut idx = index
    for _ in 0..depth bounded 64 {
        current = merkle_step(idx, current)   // native or hash-loop per target
        idx = idx >> 1
    }
    assert_digest(current, root)
}

4.6 Cost Model Framework

Every zkVM has a proving cost; the specific dimensions change but the computation framework is universal.

AspectUniversalTarget-specific
AST traversal for cost computation✅ Same algorithm
Per-instruction cost lookupDifferent cost tables
Table structureTriton: 6 tables. Miden: chiplets. Cairo: steps
Padded height (power-of-2)STARK-based VMs
--costs / --hotspots CLI✅ Same frameworkNumbers differ
Boundary proximity warningsSTARK-based VMs only

Backend interface:

trait CostModel {
    type Profile;
    fn zero() -> Self::Profile;
    fn instruction_cost(&self, op: &Op) -> Self::Profile;
    fn add(a: &Self::Profile, b: &Self::Profile) -> Self::Profile;
    fn max(a: &Self::Profile, b: &Self::Profile) -> Self::Profile;
    fn scale(p: &Self::Profile, n: usize) -> Self::Profile;
    fn padded_height(&self, p: &Self::Profile) -> u64;
    fn dominant_table(&self, p: &Self::Profile) -> String;
    fn format_report(&self, p: &Self::Profile) -> String;
}

4.7 Events System

emit Event { ... }    → Structured public output (fields visible to verifier)
seal Event { ... }    → Hashed output (only digest visible)

Events compose from I/O and hash abstractions — emit serializes to public output, seal hashes first then outputs the digest. No additional backend work needed.


5. Backend Extensions

Backend extensions are capabilities that a target VM adds to the universal core. They are not limitations or second-class features — they are the mechanism by which each backend exposes its unique power.

5.1 Extension Model

Trident Universal Core
  + Backend Extensions
  = Complete program for a specific zkVM

Programs that use backend extensions are explicitly bound to that target via #[cfg(target)] guards or target-specific module imports. The compiler enforces this:

error[E0100]: module `ext.triton.xfield` requires target `triton`
  --> main.tri:3:5
   |
3  |     use ext.triton.xfield
   |     ^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: compile with `--target triton` or remove this import

5.2 Extension Categories

Each backend may provide extensions in four categories:

CategoryWhat it providesExample
TypesAdditional primitive or composite typesXField (Triton), Felt252 (Cairo)
IntrinsicsNative VM instructions exposed as functionsxx_dot_step (Triton), mtree_set (Miden)
Inline AssemblyDirect access to target instruction setasm(triton) { dup 0 add }
Standard Library ModulesHigher-level APIs built on target capabilitiesext.triton.kernel, ext.miden.account

5.3 Triton Backend Extensions

The Triton backend extends the universal core with capabilities specific to Triton VM’s ISA and the Neptune Cash ecosystem.

Extension Types:

XField          → Cubic extension field F_p[X]/(X³−X+1)
                  3 field elements wide
                  Native arithmetic: xx_add, xx_mul, x_invert, xb_mul

Extension Intrinsics:

FunctionTASM InstructionPurpose
xx_dot_step(acc, ptr_a, ptr_b)xx_dot_stepExtension field dot product (STARK verifier inner loop)
xb_dot_step(acc, ptr_a, ptr_b)xb_dot_stepMixed-field dot product (STARK verifier inner loop)
sponge_absorb_mem(ptr)sponge_absorb_memAbsorb from RAM (combines sponge + memory read)
merkle_step_mem(ptr, idx, d)merkle_step_memMerkle step from RAM (reusable auth paths)

Extension Standard Library:

ext.triton.xfield       → XField type, arithmetic, dot products
ext.triton.kernel        → Neptune kernel interface (authenticate_field, tree_height)
ext.triton.utxo          → UTXO verification
ext.triton.stark         → Recursive STARK verifier components

Inline Assembly:

asm(triton) {
    dup 0
    add
    swap 5 pop 1
}

5.4 Miden Backend Extensions

The Miden backend extends the universal core with Miden VM’s account model and advanced advice provider capabilities.

Extension Intrinsics:

FunctionMiden InstructionPurpose
mtree_set(root, val, idx, depth)mtree_setMerkle tree update (not just verification)
adv_pipe(ptr, count)adv_pipeBatch read from advice provider to memory
exec_kernel(proc)exec.kernel::procExecute kernel procedure

Extension Standard Library:

ext.miden.account        → Miden account model (account ID, nonce, storage)
ext.miden.note           → Miden note system (create, consume, verify)
ext.miden.advice         → Extended advice provider API
ext.miden.wallet         → Wallet operations (send, receive)

Inline Assembly:

asm(miden) {
    dup.0
    add
    movdn.5 drop
}

5.5 Cairo Backend Extensions

The Cairo backend extends the universal core with StarkNet-specific capabilities and Cairo’s 252-bit field properties.

Extension Types:

Felt252         → Explicit 252-bit field element
                  (alias for Field on Cairo target, distinct type for clarity)

Extension Intrinsics:

FunctionCairo BuiltinPurpose
pedersen_hash(a, b)Pedersen builtinPedersen hash (legacy, widely used on StarkNet)
ec_point_add(p, q)EC_OP builtinElliptic curve point addition
bitwise_and(a, b)Bitwise builtinNative bitwise operations

Extension Standard Library:

ext.cairo.starknet       → StarkNet contract interface
ext.cairo.felt252        → 252-bit field specific utilities
ext.cairo.builtin        → Builtin runner access (range_check, ECDSA, etc.)

Inline Assembly:

asm(cairo) {
    [ap] = [ap-1] + [ap-2]; ap++
}

5.6 SP1/RISC Zero Backend Extensions

The RISC-V zkVM backends extend the universal core with precompile access and standard RISC-V capabilities.

Extension Intrinsics:

FunctionPrecompilePurpose
sha256(data)SHA-256 precompileSHA-256 hash (standard, not ZK-optimized)
keccak256(data)Keccak precompileKeccak-256 hash (Ethereum compatible)
secp256k1_verify(sig, msg, pk)secp256k1 precompileECDSA signature verification

Extension Standard Library:

ext.sp1.io               → SP1-specific I/O patterns
ext.sp1.precompile       → Precompile access (SHA, Keccak, secp256k1, ed25519)
ext.risczero.journal     → RISC Zero journal (public output) API

5.7 Third-Party Backend Extensions

The extension model is open. A third-party backend can define its own extensions by:

  1. Providing a target configuration TOML file
  2. Implementing the backend trait (StackBackend or RegisterBackend + IR)
  3. Publishing extension modules under ext.<target_name>/
  4. Registering intrinsics in the target’s intrinsic table

This means future zkVMs can be supported without modifying the Trident core compiler — only a new backend + extension set is needed.

5.8 Extension Usage Patterns

Portable program (no extensions):

program portable_verifier

use std.crypto.merkle

fn main() {
    let root: Digest = pub_read_digest()
    let leaf: Digest = divine_digest()
    let index: U32 = as_u32(pub_read())
    std.crypto.merkle.verify(root, leaf, index, 20)
}
// Compiles to: triton, miden, cairo, sp1

Program with backend extension (target-bound):

program triton_stark_verifier

use std.crypto.merkle
use ext.triton.xfield        // ← Binds to Triton backend
use ext.triton.stark

fn main() {
    let claim = read_claim()
    ext.triton.stark.verify(claim)
}
// Compiles to: triton only

Program with conditional extensions (multi-target with specialization):

program optimized_verifier

use std.crypto.merkle

#[cfg(triton)]
use ext.triton.xfield

fn verify_inner(commitment: Digest) {
    #[cfg(triton)]
    {
        // Use native extension field dot products for maximum performance
        let acc: XField = xfield(0, 0, 0)
        // ... optimized Triton path using xx_dot_step
    }

    #[cfg(not(triton))]
    {
        // Portable fallback using standard field arithmetic
        let acc: Field = 0
        // ... portable verification path
    }
}
// Compiles to: all targets, with Triton-optimized fast path

6. Compiler Architecture

6.1 Current Architecture (Single-Target)

Source (.tri)
  → Lexer (lexer.rs, lexeme.rs)
  → Parser (parser.rs) → AST (ast.rs)
  → Module Resolver (resolve.rs)
  → Type Checker (typeck.rs, types.rs)
  → TASM Emitter (emit.rs, stack.rs)
  → Linker (linker.rs)
  → Output: single .tasm file

6.2 Universal Architecture (Multi-Target)

Source (.tri)
  → Lexer (lexer.rs, lexeme.rs)              ← UNCHANGED
  → Parser (parser.rs) → AST (ast.rs)        ← UNCHANGED
  → Module Resolver (resolve.rs)             ← MINOR: target-aware std/ext resolution
  → Type Checker (typeck.rs, types.rs)       ← MINOR: target-parameterized constants
  → Target Configuration (target.rs)          ← NEW
  →  ┌─────────────────────────────────────┐
     │ Branch by target family              │
     │                                      │
     │  Stack VMs (Triton, Miden):          │
     │    → Direct Emitter (emit_stack.rs)  │
     │    → Linker (linker.rs)              │
     │    → Output: .tasm / .masm           │
     │                                      │
     │  Register VMs (Cairo, SP1/RZ):       │
     │    → IR Lowering (ir.rs)              ← NEW
     │    → Register Allocator (regalloc.rs) ← NEW
     │    → Target Emitter (emit_reg.rs)     ← NEW
     │    → Output: .sierra / .elf           │
     └─────────────────────────────────────┘
  → Cost Analyzer (cost.rs)                  ← MINOR: pluggable CostTable
  → Diagnostics (diagnostic.rs)              ← UNCHANGED

6.3 Shared Frontend (~80% of compiler)

ModuleLOC (est.)Changes
lexer.rs + lexeme.rs~800None
parser.rs~1200None (AST is target-agnostic)
ast.rs~400Add target-tagged asm variant
resolve.rs~300Target-aware std/ext resolution
typeck.rs~1500Parameterize DIGEST_WIDTH, FIELD_LIMBS, HASH_RATE
types.rs~200Add target constants
format.rs~600None
diagnostic.rs~200None
lsp.rs~800Target-aware completions and hover
span.rs~100None
Total shared~6100~90% unchanged

6.4 Target Configuration

Each target is defined as a TOML file shipped with the compiler:

# targets/triton.toml
[target]
name = "triton"
family = "stack"
output_extension = ".tasm"
 
[field]
name = "goldilocks"
prime = "18446744069414584321"    # 2^64 - 2^32 + 1
width = 1
limbs = 2
 
[hash]
name = "tip5"
rate = 10
digest_width = 5
 
[io]
max_batch_read = 5
max_batch_write = 5
max_batch_divine = 5
 
[memory]
word_size = 1
non_deterministic = true
write_once = false
 
[stack]
depth = 16
 
[cost]
model = "triton_6table"
 
[extensions]
types = ["XField"]
modules = ["ext.triton.xfield", "ext.triton.kernel", "ext.triton.utxo", "ext.triton.stark"]
# targets/miden.toml
[target]
name = "miden"
family = "stack"
output_extension = ".masm"
 
[field]
name = "goldilocks"
prime = "18446744069414584321"
width = 1
limbs = 2
 
[hash]
name = "rpo"
rate = 8
digest_width = 4
 
[io]
max_batch_read = 4
max_batch_write = 4
max_batch_divine = 4
 
[memory]
word_size = 1
non_deterministic = true
write_once = false
 
[stack]
depth = 16
 
[cost]
model = "miden_chiplets"
 
[extensions]
types = []
modules = ["ext.miden.account", "ext.miden.note", "ext.miden.advice", "ext.miden.wallet"]
# targets/cairo.toml
[target]
name = "cairo"
family = "register"
output_extension = ".sierra"
 
[field]
name = "stark252"
prime = "3618502788666131213697322783095070105623107215331596699973092056135872020481"
width = 1
limbs = 8
 
[hash]
name = "poseidon"
rate = 3
digest_width = 1
 
[io]
max_batch_read = 1
max_batch_write = 1
max_batch_divine = 1
 
[memory]
word_size = 1
non_deterministic = false
write_once = true
 
[registers]
count = 3
 
[cost]
model = "cairo_steps"
 
[extensions]
types = ["Felt252"]
modules = ["ext.cairo.starknet", "ext.cairo.felt252", "ext.cairo.builtin"]

6.5 Backend Trait System

Stack VM Backend:

trait StackBackend {
    fn emit_push(&mut self, value: u64);
    fn emit_pop(&mut self, count: usize);
    fn emit_dup(&mut self, depth: usize);
    fn emit_swap(&mut self, depth: usize);
    fn emit_add(&mut self);
    fn emit_mul(&mut self);
    fn emit_eq(&mut self);
    fn emit_assert(&mut self);
    fn emit_call(&mut self, label: &str);
    fn emit_return(&mut self);
    fn emit_label(&mut self, label: &str);
    fn emit_skiz(&mut self);
    fn emit_hash(&mut self);
    fn emit_divine(&mut self, count: usize);
    fn emit_read_io(&mut self, count: usize);
    fn emit_write_io(&mut self, count: usize);
    fn emit_read_mem(&mut self, count: usize);
    fn emit_write_mem(&mut self, count: usize);
    fn emit_raw(&mut self, asm: &str);       // Inline assembly passthrough
    fn emit_extension_intrinsic(&mut self, name: &str, args: &[Operand]);
    fn output_extension(&self) -> &str;
}

TritonBackend and MidenBackend share ~70% of emission logic (function prologue/epilogue, loop structure, if/else branching, stack layout). They differ in instruction mnemonics, hash operations, and extension intrinsics.

Register VM Backend (IR-based):

// Minimal SSA-like IR for register machines
enum IRInst {
    // Arithmetic
    Add { dst: Reg, lhs: Operand, rhs: Operand },
    Mul { dst: Reg, lhs: Operand, rhs: Operand },
    Inv { dst: Reg, src: Operand },
 
    // Memory
    Load { dst: Reg, addr: Operand },
    Store { addr: Operand, val: Operand },
 
    // Control flow
    Branch { cond: Operand, then_label: Label, else_label: Label },
    Jump { target: Label },
    Label(Label),
    Call { target: Label, args: Vec<Operand>, dst: Option<Reg> },
    Return { value: Option<Operand> },
 
    // ZK-specific
    PublicRead { dst: Reg },
    PublicWrite { src: Operand },
    Divine { dst: Reg },
    Assert { cond: Operand },
    Hash { dst: Reg, inputs: Vec<Operand> },
    ExtensionCall { name: String, args: Vec<Operand>, dst: Option<Reg> },
 
    // Constants
    Const { dst: Reg, value: u64 },
}

~15 instruction types. Deliberately minimal. The IR serves as the common lowering target for all register-machine backends. Each backend implements a RegisterEmitter trait that converts IR to target-specific assembly.

6.6 CLI Integration

# Build for specific targets
trident build main.tri --target triton     # Default (backward compatible)
trident build main.tri --target miden
trident build main.tri --target cairo
 
# Cost analysis per target
trident build main.tri --target triton --costs
trident build main.tri --target miden --costs
 
# Check compatibility
trident check main.tri --target miden
 
# Build for all supported targets (as listed in trident.toml)
trident build main.tri --target all

7. Standard Library Architecture

7.1 Layered Module Structure

std/
├── core/                        # Universal Core — zero VM dependencies
│   ├── array.tri                #   sum, fill, reverse, contains, index_of
│   ├── bool.tri                 #   and, or, not, xor (as field arithmetic)
│   ├── convert.tri              #   as_u32, as_field (with range checks)
│   └── math.tri                 #   min, max, abs, clamp
│
├── io/                          # Abstraction Layer — per-target intrinsic dispatch
│   ├── io.tri                   #   pub_read, pub_write, divine
│   └── mem.tri                  #   ram_read, ram_write, ram_read_block, ram_write_block
│
├── crypto/                      # Abstraction Layer — hash-parameterized
│   ├── hash.tri                 #   hash(), sponge_init/absorb/squeeze
│   ├── merkle.tri               #   verify, verify_mem, authenticate_leaf
│   └── auth.tri                 #   verify_preimage, verify_digest_preimage
│
├── assert/                      # Abstraction Layer
│   └── assert.tri               #   is_true, eq, digest
│
└── target.tri                   # Target detection constants
    pub const TARGET_NAME         #   "triton", "miden", "cairo", etc.
    pub const DIGEST_WIDTH        #   5 for Tip5, 4 for RPO, etc.
    pub const FIELD_LIMBS         #   2 for Goldilocks, 8 for 252-bit
    pub const HASH_RATE           #   10 for Tip5, 8 for RPO, 3 for Poseidon

ext/
├── triton/                      # Triton Backend Extensions
│   ├── xfield.tri               #   XField type, xx_add, xx_mul, x_invert
│   ├── stark.tri                #   xx_dot_step, xb_dot_step, recursive verifier
│   ├── kernel.tri               #   Neptune kernel interface
│   └── utxo.tri                 #   UTXO verification
│
├── miden/                       # Miden Backend Extensions
│   ├── account.tri              #   Miden account model
│   ├── note.tri                 #   Miden note system
│   ├── advice.tri               #   Extended advice provider API
│   └── wallet.tri               #   Wallet operations
│
├── cairo/                       # Cairo Backend Extensions
│   ├── starknet.tri             #   StarkNet contract interface
│   ├── felt252.tri              #   252-bit field utilities
│   └── builtin.tri              #   Builtin runner access
│
└── sp1/                         # SP1 Backend Extensions
    ├── precompile.tri           #   SHA-256, Keccak, secp256k1, ed25519
    ├── io.tri                   #   SP1-specific I/O patterns
    └── journal.tri              #   RISC Zero journal API

7.2 Cross-Target Standard Library Implementation

Standard library modules use multi-target intrinsic annotations with portable fallbacks:

// std/crypto/hash.tri
module std.crypto.hash

/// Hash RATE field elements into a Digest.
/// Dispatches to the target VM's native hash instruction.
#[intrinsic(triton::hash)]
#[intrinsic(miden::hperm)]
pub fn hash_native(input: [Field; HASH_RATE]) -> Digest

/// Initialize sponge state.
#[intrinsic(triton::sponge_init)]
#[intrinsic(miden::hperm_init)]
pub fn sponge_init()

/// Absorb RATE elements into sponge.
#[intrinsic(triton::sponge_absorb)]
#[intrinsic(miden::hperm_absorb)]
pub fn sponge_absorb(input: [Field; HASH_RATE])

/// Squeeze RATE elements from sponge.
#[intrinsic(triton::sponge_squeeze)]
#[intrinsic(miden::hperm_squeeze)]
pub fn sponge_squeeze() -> [Field; HASH_RATE]

The compiler selects the intrinsic matching the current target. If no intrinsic matches, the function body provides a portable fallback.

7.3 Fallback Pattern

// std/crypto/merkle.tri — native on Triton/Miden, software on others
module std.crypto.merkle

use std.crypto.hash

pub fn verify(root: Digest, leaf: Digest, index: U32, depth: U32) {
    let mut current = leaf
    let mut idx = index
    for _ in 0..depth bounded 64 {
        current = merkle_step_impl(idx, current)
        idx = idx >> 1
    }
    assert_digest(current, root)
}

/// Native on Triton/Miden; hash-loop fallback on others.
#[intrinsic(triton::merkle_step)]
#[intrinsic(miden::mtree_get)]
fn merkle_step_impl(idx: U32, current: Digest) -> Digest {
    // Portable fallback: divine sibling, hash in correct order
    let sibling: Digest = divine_digest()
    if idx & 1 == 0 {
        hash_pair(current, sibling)
    } else {
        hash_pair(sibling, current)
    }
}

When a native intrinsic exists, the function body is ignored and the native instruction is emitted. When no intrinsic matches, the body compiles normally. Native performance where available; correct behavior everywhere.


8. Language Extensions Required

8.1 Backward-Compatible Changes

ExtensionDescriptionEffort
Target-tagged asm blocksasm(triton) { ... } alongside existing asm { ... }2 days
Multi-target intrinsicsMultiple #[intrinsic] annotations per function2 days
Target constantsDIGEST_WIDTH, FIELD_LIMBS, HASH_RATE from config1 day
--target CLI flagSelect compilation target1 day
Target-specific #[cfg]#[cfg(triton)], #[cfg(miden)], etc.Already implemented
ext.* module namespaceBackend extension modules1 day

8.2 Breaking Changes (Managed via Edition)

ChangeImpactMigration
Digest width target-dependentHardcoded index d[4] may break on Miden (4 elements)Use DIGEST_WIDTH constant
split() returns [U32; FIELD_LIMBS]Tuple destructuring let (hi, lo) = split(x) breaks on CairoUse array indexing; split_lo()/split_hi() helpers for Goldilocks compat
sponge_absorb() arity changes10 args on Triton, 8 on Miden, 3 on CairoArray argument: sponge_absorb(elements: [Field; HASH_RATE])
Bare asm { } deprecatedNeeds target tag for multi-target buildsBare asm { } treated as asm(triton) { } with deprecation warning

Edition strategy:

[project]
name = "my_project"
edition = "2026"            # Opt into multi-target semantics
entry = "main.tri"

Programs without an edition default to Triton-compatible behavior. Edition "2026" activates multi-target semantics.


9. Implementation Plan

9.1 Phase 0 — Internal Refactoring (No New Targets)

Duration: 2-3 weeks | Risk: Zero — no external-facing changes

Restructure compiler internals to support pluggable backends without changing any output.

TaskFiles affectedEffort
Extract TargetConfig struct from hardcoded constantsNew target.rs2 days
Refactor emit.rsemit_stack.rs + StackBackend traitemit.rs3 days
Refactor stack.rs to accept stack_depth parameterstack.rs1 day
Refactor cost.rs to accept pluggable CostTablecost.rs2 days
Parameterize typeck.rs for target constantstypeck.rs, types.rs2 days
Add --target CLI flag (only triton accepted)main.rs1 day
Add target TOML loadingtarget.rs1 day
Restructure std/ into layered directories, create ext/std/, resolve.rs1 day
Validation: all 350+ existing tests pass unchanged1 day

Deliverable: Same compiler, same output, cleaner architecture. TritonBackend is the only backend but accessed through the trait interface.

9.2 Phase 1 — Miden VM Backend

Duration: 6-8 weeks | Prerequisite: Phase 0

Why Miden first: same field (Goldilocks), same architecture (stack, 16-element), same proof system family (STARK). Maximum code reuse, minimum risk.

TaskEffort
Create targets/miden.toml1 day
Implement MidenBackend (StackBackend trait)2 weeks
Adapt hash intrinsics (RPO instead of Tip5)1 week
Implement Miden cost model (chiplet-based)1 week
Merkle operations → mtree_get / mtree_set3 days
I/O mapping (advice provider)3 days
Miden linker (MAST program packaging)1 week
Miden backend extension modules (ext.miden.*)3 days
Port examples to dual-target1 week
Test suite (~100 new tests)1 week

Deliverable: trident build --target miden produces valid Miden Assembly. Programs using only universal core + abstraction layer compile to both targets unchanged.

9.3 Phase 2 — Cairo/Sierra Backend

Duration: 3-4 months | Prerequisite: Phase 1

Introduces the register-machine IR and first non-stack backend.

TaskEffort
Design and implement IR (ir.rs)2 weeks
AST → IR lowering3 weeks
Register allocator for Cairo’s AP/FP model2 weeks
Sierra IR emitter3 weeks
252-bit field support in type checker1 week
Poseidon hash intrinsics1 week
Write-once memory model validation3 days
Cairo cost model (steps-based)1 week
Cairo backend extension modules (ext.cairo.*)1 week
Test suite (~150 new tests)2 weeks

Deliverable: trident build --target cairo produces valid Sierra IR. The IR infrastructure enables SP1/RISC Zero with significantly less effort.

9.4 Phase 3 — SP1/RISC Zero Backend

Duration: 3-4 months | Prerequisite: Phase 2 (reuses IR)

TaskEffort
RISC-V rv32im emitter (from IR)4 weeks
RISC-V register allocator (32 registers)2 weeks
SP1/RISC Zero precompile mapping (SHA, Keccak, secp256k1)2 weeks
ELF output packaging1 week
SP1 I/O syscall mapping1 week
SP1 backend extension modules (ext.sp1.*)1 week
Test suite (~150 new tests)2 weeks

9.5 Phase 4 — NockVM (Exploratory)

Duration: TBD (research phase) | Prerequisite: Phase 2+

TaskEffort
Research Nock compilation targets and Zorp proving model2 weeks
Prototype: Trident subset → Nock formulas (arithmetic + conditionals)4 weeks
Evaluate feasibility of full backend2 weeks
Decision point: proceed to full backend or park

9.6 Timeline Summary

Month 1-2:    Phase 0 (refactor) + Phase 1 start (Miden)
Month 2-3:    Phase 1 complete (Miden backend shipping)
Month 4-6:    Phase 2 (Cairo/Sierra backend)
Month 7-9:    Phase 3 (SP1/RISC Zero backend)
Month 10+:    Phase 4 (NockVM research) + ecosystem maturation

10. Testing Strategy

10.1 Equivalence Testing

For programs using only universal core + abstraction layer, the same source must produce identical results across all targets:

  1. Compile for all supported targets
  2. Execute on each VM (or emulator) with identical inputs
  3. Compare public outputs for exact equality
  4. Report divergence as compiler bug
trident test main.tri --target all --equivalence

10.2 Test Suite Structure

CategoryCount (est.)Scope
Universal core~200Run on ALL targets
Abstraction layer~100Run on ALL targets; verify semantic equivalence
Hash/Merkle~50 per targetRun per-target; verify against reference implementations
Backend extensions~30 per targetRun only on matching target
Existing regression~350Triton regression suite (must never break)
Total~800+

10.3 Cross-Target Fuzzing

Property-based fuzzing for multi-target correctness:

  • Generate random valid Trident programs (universal core + abstraction layer only)
  • Compile to all supported targets
  • Execute on each VM
  • Assert output equivalence

Catches semantic divergences between backends that unit tests might miss.


11. Success Criteria

Phase 1 (Miden)

  • trident build --target miden produces valid Miden Assembly for all universal programs
  • Merkle verification runs correctly on both Triton and Miden from same source
  • Cost reports show Miden-specific chiplet structure
  • Existing Triton programs compile unchanged with --target triton
  • At least 5 non-trivial programs compile and verify on both targets
  • Compiler size under 15,000 lines (from ~12,000)

Phase 2 (Cairo)

  • trident build --target cairo produces valid Sierra IR
  • IR is clean enough that a new register-machine backend can be added in <6 weeks
  • Universal programs produce equivalent results across Triton, Miden, and Cairo
  • Cairo cost model correctly reflects step-based proving cost

Overall

  • Developer can write a Merkle verifier once and deploy to any supported zkVM
  • Adding a new stack-machine target takes <4 weeks
  • Adding a new register-machine target takes <8 weeks (with IR reuse)
  • Third-party backends can be added without modifying compiler core
  • Trident is the most auditable multi-target ZK compiler available

12. Risk Analysis

RiskLikelihoodImpactMitigation
Field mismatch causes semantic divergenceMediumHighExtensive equivalence testing; programs should never depend on specific modulus
Miden Assembly format changes (pre-1.0)MediumMediumPin to specific Miden version; abstract behind trait
IR complexity exceeds “minimal”LowHighStrict budget (~20 instruction types max); resist feature creep
Cost model abstraction loses precisionMediumMediumEach target keeps its own cost table; framework shared, numbers are not
Register allocator bugs (Cairo/RISC-V)MediumHighProven algorithms (linear scan); extensive fuzzing
Target VM breaking changes (pre-1.0 VMs)HighMediumVersion-pin targets; compatibility layers
Extension proliferation fragments ecosystemMediumMediumCore standard library must remain universal; extensions are opt-in

13. Complete Feature Portability Matrix

#FeatureLayerTritonMidenCairoSP1/RZNockVM
Types
1Field (native field element)Core
2BoolCore
3U32Core
4[T; N] fixed arraysCore
5(T1, T2) tuplesCore
6structCore
7Digest ([Field; DIGEST_WIDTH])Abstraction
8XField (cubic extension)Extension
9Felt252 (explicit 252-bit)Extension
Field Arithmetic
10a + b (field add)Core
11a * b (field mul)Core
12inv(a)Core
13neg(a) / sub(a, b)Core
14a == bCore
15split(a)[U32; FIELD_LIMBS]Abstraction
Integer Arithmetic
16a < b (u32 compare)Core
17a & b, a ^ b (bitwise)Core
18a /% b (divmod)Core
19log2, pow, popcountCore
Control Flow
20if / elseCore
21for bounded loopsCore
22match expressionsCore
23assert() / assert_eq()Core
24assert_digest()Abstraction
Functions & Modules
25fn definitionsCore
26pub / private visibilityCore
27module / useCore
28const declarationsCore
29Size-generic fn<N>()Core
30#[cfg()]Core
31#[test]Core
I/O
32pub_read() / pub_write()Abstraction
33divine()Abstraction
34pub input / pub output / sec inputAbstraction
Memory
35ram_read() / ram_write()Abstraction⚠️¹
36ram_read_block / ram_write_blockAbstraction⚠️¹
Cryptographic Primitives
37hash()DigestAbstraction
38sponge_init/absorb/squeezeAbstraction⚠️²⚠️
39merkle.verify()Abstraction
Events
40emit Event { ... }Abstraction
41seal Event { ... }Abstraction
Cost Model
42--costs / --hotspotsAbstraction
43--annotate / --compareAbstraction
Backend Extensions
44XField arithmeticExtension
45xx_dot_step / xb_dot_stepExtension
46sponge_absorb_memExtension
47merkle_step_memExtension
48Neptune kernel / UTXOExtension
49Miden account modelExtension
50Miden note systemExtension
51Pedersen hashExtension
52EC point operationsExtension
53SHA-256 / Keccak precompileExtension
54secp256k1 verificationExtension
55Inline assemblyExtension✅³✅³✅³✅³✅³

Notes: ¹ Cairo memory is write-once; compiler warns on multiple writes to same address ² Software sponge implementation via precompile; higher cost than native ³ Target-tagged: asm(triton) { ... }, asm(miden) { ... }, etc. Each backend accepts only its own assembly syntax

Layer Summary

LayerFeature count% of languageDescription
Universal Core3156%Compiles identically to all targets
Abstraction Layer1222%Same syntax, per-target dispatch
Backend Extensions12+22%Target-specific capabilities (open-ended)

Trident Universal — Write once, prove anywhere. This document is a living design. Extensions are driven by ecosystem needs and validated by working implementations.