Equality Constraints

So like =A4 in Excel? Yes exactly.

Equality Constraints

Equality constraints allow us to enforce that two arbitrary cells in the spreadsheet are equal.

Fixing The Soundness Issue

We are going to use these to fix the issue in the previous section: enforcing that w0 and w1 are equal to lhs and rhs respectively. In order to enforce equality between cells, we need to enable equality constraints on the columns we want to enforce equality on.

In our example we only have a single advice column:

    fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
        // let q_enable = meta.fixed_column();
        let q_enable = meta.complex_selector();
        let advice = meta.advice_column();

        // enable equality constraints
        meta.enable_equality(advice);

And we need to add equality constraints between the cells when we assign regions:

    /// This region occupies 3 rows.
    fn mul(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        lhs: AssignedCell<F, F>,
        rhs: AssignedCell<F, F>,
    ) -> Result<AssignedCell<F, F>, Error> {
        layouter.assign_region(
            || "mul",
            |mut region| {
                let w0 = lhs.value().cloned();
                let w1 = rhs.value().cloned();
                let w2 =
                    w0 //
                        .and_then(|w0| w1.and_then(|w1| Value::known(w0 * w1)));

                let w0 = region.assign_advice(|| "assign w0", config.advice, 0, || w0)?;
                let w1 = region.assign_advice(|| "assign w1", config.advice, 1, || w1)?;
                let w2 = region.assign_advice(|| "assign w2", config.advice, 2, || w2)?;
                config.q_enable.enable(&mut region, 0)?;

                // enforce equality between the w0/w1 cells and the lhs/rhs cells
                region.constrain_equal(w0.cell(), lhs.cell())?;
                region.constrain_equal(w1.cell(), rhs.cell())?;

                Ok(w2)
            },
        )
    }

Because manually assigning a value from another cell, only to then enforce equality to the same cell, is very common, cumbersome and error-prone, Halo2 provides a handy function which "copies" one cell to another:

// enforce equality between the w0/w1 cells and the lhs/rhs cells
let _w0 = lhs.copy_advice(|| "assign w0", &mut region, config.advice, 0)?;
let _w1 = rhs.copy_advice(|| "assign w1", &mut region, config.advice, 1)?;
let w2 = region.assign_advice(|| "assign w2", config.advice, 2, || w2)?;
config.q_enable.enable(&mut region, 0)?;

It's simply syntactic sugar for the above code.

Exercises

The full code is available at the bottom of the page.

Exercise

Exercise: Try reimplementing the attack from the previous section.

Does the circuit reject the assignment now?

use std::marker::PhantomData;

use halo2_proofs::{
    circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value},
    dev::MockProver,
    plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Selector},
    poly::Rotation,
};

use ff::Field;

struct TestCircuit<F: Field> {
    _ph: PhantomData<F>,
    secret: Value<F>,
}

#[derive(Clone, Debug)]
struct TestConfig<F: Field + Clone> {
    _ph: PhantomData<F>,
    q_enable: Selector,
    advice: Column<Advice>,
}

impl<F: Field> TestCircuit<F> {
    // ANCHOR: mul
    /// This region occupies 3 rows.
    fn mul(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        lhs: AssignedCell<F, F>,
        rhs: AssignedCell<F, F>,
    ) -> Result<AssignedCell<F, F>, Error> {
        layouter.assign_region(
            || "mul",
            |mut region| {
                let w0 = lhs.value().cloned();
                let w1 = rhs.value().cloned();
                let w2 =
                    w0 //
                        .and_then(|w0| w1.and_then(|w1| Value::known(w0 * w1)));

                let w0 = region.assign_advice(|| "assign w0", config.advice, 0, || w0)?;
                let w1 = region.assign_advice(|| "assign w1", config.advice, 1, || w1)?;
                let w2 = region.assign_advice(|| "assign w2", config.advice, 2, || w2)?;
                config.q_enable.enable(&mut region, 0)?;

                // ANCHOR: enforce_equality
                // enforce equality between the w0/w1 cells and the lhs/rhs cells
                region.constrain_equal(w0.cell(), lhs.cell())?;
                region.constrain_equal(w1.cell(), rhs.cell())?;
                // ANCHOR_END: enforce_equality

                Ok(w2)
            },
        )
    }
    // ANCHOR_END: mul

    /// This region occupies 3 rows.
    #[rustfmt::skip]
    fn mul_sugar(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        lhs: AssignedCell<F, F>,
        rhs: AssignedCell<F, F>,
    ) -> Result<AssignedCell<F, F>, Error> {
        layouter.assign_region(
            || "mul",
            |mut region| {
                let w0 = lhs.value().cloned();
                let w1 = rhs.value().cloned();
                let w2 =
                    w0 //
                        .and_then(|w0| w1.and_then(|w1| Value::known(w0 * w1)));

// ANCHOR: copy
// enforce equality between the w0/w1 cells and the lhs/rhs cells
let _w0 = lhs.copy_advice(|| "assign w0", &mut region, config.advice, 0)?;
let _w1 = rhs.copy_advice(|| "assign w1", &mut region, config.advice, 1)?;
let w2 = region.assign_advice(|| "assign w2", config.advice, 2, || w2)?;
config.q_enable.enable(&mut region, 0)?;
// ANCHOR_END: copy

            Ok(w2)
        },
    )
}

    /// This region occupies 1 row.
    fn free(
        config: &<Self as Circuit<F>>::Config,
        layouter: &mut impl Layouter<F>,
        value: Value<F>,
    ) -> Result<AssignedCell<F, F>, Error> {
        layouter.assign_region(
            || "free variable",
            |mut region| {
                let w0 = value;
                let w0 = region.assign_advice(|| "assign w0", config.advice, 0, || w0)?;
                Ok(w0)
            },
        )
    }
}

impl<F: Field> Circuit<F> for TestCircuit<F> {
    type Config = TestConfig<F>;
    type FloorPlanner = SimpleFloorPlanner;

    fn without_witnesses(&self) -> Self {
        TestCircuit {
            _ph: PhantomData,
            secret: Value::unknown(),
        }
    }

    // ANCHOR: enable_equality
    fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
        // let q_enable = meta.fixed_column();
        let q_enable = meta.complex_selector();
        let advice = meta.advice_column();

        // enable equality constraints
        meta.enable_equality(advice);
        // ANCHOR_END: enable_equality

        // ANCHOR: new_gate
        // define a new gate:
        //
        // Advice
        // |      w0 |
        // |      w1 |
        // | w0 * w1 |
        meta.create_gate("vertical-mul", |meta| {
            let w0 = meta.query_advice(advice, Rotation(0));
            let w1 = meta.query_advice(advice, Rotation(1));
            let w3 = meta.query_advice(advice, Rotation(2));
            let q_enable = meta.query_selector(q_enable);
            vec![q_enable * (w0 * w1 - w3)]
        });
        // ANCHOR: new_gate

        TestConfig {
            _ph: PhantomData,
            q_enable,
            advice,
        }
    }

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

        // do a few multiplications
        let a2 = TestCircuit::<F>::mul(&config, &mut layouter, a.clone(), a.clone())?;
        let a3 = TestCircuit::<F>::mul(&config, &mut layouter, a2.clone(), a.clone())?;
        let _a5 = TestCircuit::<F>::mul(&config, &mut layouter, a3.clone(), a2.clone())?;

        Ok(())
    }
}

fn main() {
    use halo2_proofs::halo2curves::bn256::Fr;

    // run the MockProver
    let circuit = TestCircuit::<Fr> {
        _ph: PhantomData,
        secret: Value::known(Fr::from(1337u64)),
    };
    let prover = MockProver::run(8, &circuit, vec![]).unwrap();
    prover.verify().unwrap();
}