Components and Templates
C++ has Classes. Solidity has Contracts. Circom has Components.
What are Components / Templates?
Templates are the definition of generic, indivisible circuits in Circom. Components are the instantiation of these templates inside of other circuits. Templates provide the mechanism for creating API's using Signals in Circom. That is, by defining logic within templates, components can be extended to quickly specify a set logical pattern/ action within your circuits.
Though we draw the analogy to classes/ contracts, templates do not provide any sort of object-oriented programming.
The Main Component
The main component defines the circuit you intend to construct your Zero Knowledge proofs with. The main component can contain arbitrarily many components within its circuit, however there can only be one main component at the top level.
As mentioned in the Signals and Variables section, Circom developers specify their public inputs when they declare a template is the main component. This is shown below, where we want inPub
to be a public input and inPriv
to be a shielded, private input:
Note that your entire code base is not limited to one main component. Your app's intended functionality may rely on multiple different proofs, in which each proof has its own main component. In BattleZips, both the shot proof and the board proof are main components.
Template Parameters
Oftentimes you will find that your template's logic needs the Circom equivalent of a generic parameter. The values are still integers on the field, but during the execution of a component you can supply constant values that are parameterized at the template level.
Let's take the example of a num2bits
decomposition component.
In this case, we provide the template parameter n
to define the number of bits in the number we are decomposing into a bit array. We want to build modular tool that can be extensively reused with few barriers; by using template parameters we can make generic circuits that can be molded for their specific application.
You can also use template parameters with the main component. This can be seen in the RollupNC code base:
This allows us to configure a specific constant to use in our Zero Knowledge proof computation while also maintaining a degree of modularity over our implementations. Practically, for the example of RollupNC, we use small account and transaction trees since we are using this code to run unit tests. However, we could very easily scale to production size by simply changing the Main component to hold millions of accounts with hundreds of transactions per batch by defining the main component with the parameters:
Working with Components
In the case of num2bits shown above,
we can see how it is employed in BattleZips (code abridged here, see full source)
In the above case, we assign a variable with the component's output. However, you are just as able to use the <--
and <==
operators to assign signals with a component's output.
When using a component, all inputs MUST be assigned before attempting to assign another signal using the output of the component. For instance, if in the above code we only assigned 99 of the 100 bits to toBits.in
, then tried to access toBits.out
, we would experience a compile-time error.
Anonymous Components
Circom 2.1 included the addition of anonymous components. Much like the concept of anonymous functions, the Circom docs define anonymous components as such:
An anonymous component allows in a single instruction 1) the implicit declaration of a new component, 2) the assignment of every input signal and, finally, 3) the assignment of every output signal.
In simple terms, there are times when we can treat a component like a throwaway function where in one shot we want to extract an output given an input. In previous versions of Circom, whether you want to repeatedly access a component or only need it once, you must declare the component as shown below:
In template B, we do not care about the component temp_a
and do not need such a verbose API to access it. Anonymous templates mean that we do not need to declare temp_a
at all, streamlining the code to look like:
Last updated