Fixed Columns

Fixing cells in the spreadsheet.

It is very useful to be able to fix certain cells in the spreadsheet to a constant value. This is the goal of the Column::Fixed column type which, as the name suggests, is a column containing values fixed by the verifier which the prover cannot change.

We have in fact already encountered this column type, albeit under a different name: selector columns, e.g. q_enable, are fixed columns with a fixed value of 0 or 1. The Column::Fixed column type is more general, allowing any fixed value.

Constants in Gates

So now let us use this to enable checks against constants. One way to use fixed columns is to "program" gates, the simplest example of this is a gate which checks a cell against a constant.

To do so, we introduce a new selector, a new fixed column and a gate to match:

        // selector for the fixed column
        let q_fixed = meta.complex_selector();

        // add a new fixed column
        let fixed = meta.fixed_column();

        meta.create_gate("equal-constant", |meta| {
            let w0 = meta.query_advice(advice, Rotation::cur());
            let c1 = meta.query_fixed(fixed, Rotation::cur());
            let q_fixed = meta.query_selector(q_fixed);
            vec![q_fixed * (w0 - c1)]
        });

Here, q_fixed * (w0 - c1) is zero iff:

  • q_fixed is zero, in which case the gate is disabled.
  • w0 is equal to the cell c1 in the fixed column.

To use this gate we use an equality constraint to enforce that w0 matches the AssignedCell, assign the constant and turn on the gate:

    /// This region occupies 1 row.
    fn fixed(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        value: F,
        variable: AssignedCell<F, F>,
    ) -> Result<(), Error> {
        layouter.assign_region(
            || "fixed",
            |mut region| {
                variable.copy_advice(
                    || "assign variable", //
                    &mut region,
                    config.advice,
                    0,
                )?;
                region.assign_fixed(
                    || "assign constant",
                    config.fixed, //
                    0,
                    || Value::known(value),
                )?;

                // turn the gate on
                config.q_fixed.enable(&mut region, 0)?;
                Ok(())
            },
        )
    }

With this new gate, we can finally enforce some non-trivial constraints:

    fn synthesize(
        &self,
        config: Self::Config, //
        mut layouter: impl Layouter<F>,
    ) -> Result<(), Error> {
        let a = TestCircuit::<F>::free(&config, &mut layouter, self.secret.clone())?;

        // a2 = a * a = a^2
        let a2 = TestCircuit::<F>::mul(
            &config,
            &mut layouter, //
            a.clone(),
            a.clone(),
        )?;

        // a3 = a2 * a = a^3
        let a3 = TestCircuit::<F>::mul(
            &config,
            &mut layouter, //
            a2.clone(),
            a.clone(),
        )?;

        // a5 = a3 * a2 = a^5
        let a5 = TestCircuit::<F>::mul(
            &config,
            &mut layouter, //
            a3.clone(),
            a2.clone(),
        )?;

        // fix the value 1337
        TestCircuit::<F>::fixed(
            &config,
            &mut layouter, //
            F::from_u128(4272253717090457),
            a5,
        )?;

        Ok(())
    }

We will see much more complex examples of "programming" gates in subsequent sections. Namely, in the exercises of the "chips" section.

Constants in Equality Constraints

Another, less common, use of fixed columns is to enforce equality between a cell and a constant by enabling equality constraints on a fixed column. Using this strategy, we do not need a separate gate, we simply define a fixed column and enable equality constraints on it:

        // add a new fixed column
        let fixed = meta.fixed_column();

        // turn on equality constraints for the fixed column
        meta.enable_constant(fixed);

We can then use this by assigning the desired constant to the fixed column and enforcing equality using region.constrain_equal:

    /// This region occupies 1 row.
    fn fixed(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        value: F,
        variable: AssignedCell<F, F>,
    ) -> Result<(), Error> {
        layouter.assign_region(
            || "fixed",
            |mut region| {
                // add the value to the fixed column
                // if the same constant is used multiple times,
                // we could optimize this by caching the cell
                let fixed_cell = region.assign_fixed(
                    || "assign fixed",
                    config.fixed,
                    0,
                    || Value::known(value),
                )?;
                region.constrain_equal(variable.cell(), fixed_cell.cell())?;
                Ok(())
            },
        )
    }