Layouter -> Gates

Buckle up, things start to get a bit more complex now. In this section we will get to see some engineering tools that were built to let us implement theoretical computer science in practice.

The Layouter trait works together with the Region struct and RegionLayouter trait to allow us to define the gate functionality that we want.

Layouter's function assign_region(Layouter, name, assignment) essentially allows us to define how each gate or property we want will work. The entry assignment is a mutable function that does most of the work here, its output is used to help create the output of assign_region itself.

Generally speaking, the mutable function that is assignment will call a few other functions that we want to briefly introduce. Recall that we chose a main equation to define the functionality that we wanted to see, so the layouter is going to help us assign values correctly within this equation (i.e. by assigning values to the columns correctly) based on what gate we want to see.

For example, to assign a witness value (a value to one of the wires, not a selector), we generally use the assign_advice(Region, annotation, column, offset, to) function implemented on the Region struct. And this function in turn calls the assign_advice(RegionLayouter, annotation, column, offset, to) function defined in the RegionLayouter trait. What these function calls achieve is the insertion of a value in a column of type advice. Which column? Well that is determined by the column entry in the assign_advice function.

To assign a selector value, we will instead use the function assign_fixed(), and the reasoning/structure here is essentially the same.

Relax

Those last few paragraphs were extremely dense. You may have to go over them again, but perhaps do that after reading the rest of this section. They are in a sense a summary of what is explained next.

Core idea

Let's recap what we want to achieve in terms of functionality the multiplication gate that we want to build. What was our equation again?

lā‹…sl+rā‹…sr+(lā‹…r)ā‹…smāˆ’oā‹…so+sc+PI=0.l\cdot s_l + r\cdot s_r + (l\cdot r)\cdot s_m - o\cdot s_o + s_c + PI = 0.

How should we set our selectors in order to achieve multiplication of ll and rr? Well, the multiplication value is selected by sms_m, so let's set that to one. What else? Well, we need to ensure the output oo comes out to be the multiplication value, so we should set sos_o to 1. That seems to be it for things we want on, so all other selector values should be set to 0 (this should happen by default).

Question: what happens if instead of setting sms_m to 1 we set it to 2 (or any other value)?

We have figured out what values to insert in the columns representing the selector values, which leaves us with the advice columns (l,r,o) and a public input column (of type instance). We'll probably need to insert the correct values for l,r based on the computation we want to do, and then work out the correct value for o, which in this case should be the product of the previous 2 values. s_c should just be set to 0 in this gate because it is not relevant right now; it is to allow the addition of a constant (and would therefore be the same in all uses of a circuit). PI should also be 0, it represents a public input which can be different for each proof made on the same circuit (for example, perhaps it could represent a public key).

We have now covered how we need to assign values to columns in order to represent our gates. But recall that the assign_region doesn't actually take in values, and so where do we get the values to put into the columns?

Hold on here, I know things are getting quite complex but we are almost done.

Wherever we are using layouter.assign_region, in that code block we should have the values we want to put into the columns, say values (x,y,z)(x,y,z). One way we can achieve this, as you will see in the examples, is to call this function inside a function that we are defining in the composer trait introduced in the previous section.

So, in the composer trait TutorialComposer, let's define the function raw_multiplication and give it the inputs (I know we already saw this, but le't actually go over it a bit more now)

When we implement the above function, we will call layouter.assign_region, and the function f shown above as an entry to raw_multiplication will be what we use to get the values (x,y,z)(x,y,z) that we want to put in the columns - notice that this function is of type FM which returns three assigned cells, each with a field value.

Note that since this mutable function takes no entries we will have to define how to combine our values through the mutable function f in raw_multiply. For example, we can do that as in the image below (i.e. when we call the raw_multiply function we give it the wire values)

Okay, I know that was a lot. Let's try to put that understanding together with some code. And be happy in the knowledge that we have covered a lot of the complexity already - you'll be able to make your own circuits in no time! And don't be afraid to reread this stuff, it is dense and takes a bit of time.

Last updated