Skip to content

Wires

Here, we explore wires—the fundamental connections that link gates together in your circuits.

Wires are essentially indices into a statically-allocated witness array. When you construct a circuit, each wire receives a unique index. Later, during proving, the prover populates this array with concrete values, and gates read their inputs and write their outputs at these predetermined positions. Let's look at the different types of wires and how to use them effectively.

Wire Types

Public Wires define the circuit's external interface. Both parties observe these values - the prover supplies them as inputs and outputs, while the verifier includes them in the proof transcript to bind the proof to specific public parameters.

let input = builder.add_inout();     // Public input from caller
let output = builder.add_inout();    // Public output to verify

Private Wires encapsulate the prover's private knowledge. The verifier observes only cryptographic commitments to these values, never the values themselves. These wires carry secret inputs, intermediate computations, and any data requiring privacy while maintaining verifiability.

let secret_key = builder.add_witness();      // Prover's secret
let intermediate = builder.add_witness();    // Private computation

Constant Wires When you need compile-time values in your circuit, constant wires embed them directly into the constraint system. The compiler maintains a constant pool with automatic deduplication—if you request the same constant twice, you'll get the same wire index back. This optimization takes advantage of how frequently circuits reuse masks, offsets, and sentinel values.

let mask = builder.add_constant_64(0xFF00FF00);  // Creates wire if new
let zero = builder.add_constant_64(0);           // Returns existing wire
let mask2 = builder.add_constant_64(0xFF00FF00); // Same wire as 'mask'

Gate Output Wires

Each gate operation automatically allocates its own output wires during circuit construction and returns references to them. The number of outputs depends on the gate's opcode: most gates produce one output, while operations like imul produce two (high and low words).

let xor_result = builder.bxor(a, b);        // Gate creates 1 output wire
let (high, low) = builder.imul(x, y);       // Gate creates 2 output wires
let sum = builder.iadd_32(p, q);            // Gate creates 1 output wire

Wires and Compiler Optimizations

The compiler transforms the witness array layout and constraint structure while preserving circuit semantics. These passes reduce both witness size and constraint count transparently:

  • Wire reordering: Segregates wires by visibility (constants, public, private) and aligns each section to power-of-two boundaries for cache-efficient access
  • Gate fusion: Substitutes intermediate XOR expressions directly into consuming constraints, eliminating temporary wire allocations
  • Constant propagation: Evaluates compile-time expressions and replaces wire references with computed constants
  • Dead code elimination: Prunes constraints whose outputs become unused after fusion passes