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 cellc1
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(())
},
)
}