Zero knowledge virtual machines frequently make use of lookup arguments to enable performance optimizations. Miden VM uses multiset checks, and a brief introduction to them can be found here.
In Miden VM, multiset checks are used for two purposes:
- To prove the consistency of intermediate values that must persist between different cycles of the trace, without storing the full data in the execution trace (which would require adding more columns to the trace).
- To prove correct interaction between two independent sections of the execution trace, e.g., between the main trace where the result of some operation is required, but would be expensive to compute, and a specialized component which can perform that operation cheaply.
The first is achieved using virtual tables of data, where we add a row at some cycle in the trace and remove it at a later cycle when it is needed again. Instead of maintaining the entire table in the execution trace, multiset checks allow us to prove data consistency of this table using one running product column.
The second is done by reducing each operation to a lookup value and then using a communication bus, implemented as a running product column, to provably connect the two sections of the trace.
Running product columns
Although the multiset equality check can be thought of as comparing multiset equality between two vectors and , in Miden VM it is implemented as a single running product column in the following way:
- The running product column is initialized to a value at the beginning of the trace. (We typically use .)
- All values of are multiplied into the running product column.
- All values of are divided out of the running product column.
- If and were multiset equal, then the running product column will equal at the end of the trace.
Running product columns are computed using a set of random values , sent to the prover by the verifier after the prover commits to the execution trace of the program.
Length of running product columns
Running product columns are computed by including information from the current row of the main execution trace into the next row of the running product column. Thus, in order to ensure that the trace is long enough to give the running product column space for its final value, a padding row may be required at the end of the trace of the component upon which the running product column depends.
This is true when the data in the main trace could go all the way to the end of the trace, such as in the case of the range checker.
Cost of running product columns
It is important to note that depending on the field in which we operate, a running product column may actually require more than one trace column. This is specifically true for small fields.
Since Miden uses a 64-bit field, each running product column needs to be represented by columns to achieve ~100-bit security and by columns to achieve ~128-bit security.
Virtual tables can be used to store intermediate data which is computed at one cycle and used at a different cycle. When the data is computed, the row is added to the table, and when it is used later, the row is deleted from the table. Thus, all that needs to be proved is the data consistency between the row that was added and the row that was deleted.
The consistency of a virtual table can be proved with a single trace column , which keeps a running product of rows that were inserted into and deleted from the table. This is done by reducing each row to a single value, multiplying the value into when the row is inserted, and dividing the value out of when the row is removed. Thus, at any step of the computation, will contain a product of all rows currently in the table.
The initial value of is set to 1. Thus, if the table is empty by the time Miden VM finishes executing a program (we added and then removed exactly the same set of rows), the final value of will also be equal to 1. The initial and final values are enforced via boundary constraints.
Computing a virtual table's trace column
To compute a product of rows, we'll first need to reduce each row to a single value. This can be done as follows.
Let be columns in the virtual table, and assume the verifier sends a set of random values , to the prover after the prover commits to the execution trace of the program.
The prover reduces row in the table to a single value as:
Then, when row is added to the table, we'll update the value in the column like so:
Analogously, when row is removed from the table, we'll update the value in column like so:
Virtual tables in Miden VM
Miden VM currently makes use of 6 virtual tables across 4 components:
- Range checker:
- Hash chiplet:
One strategy for improving the efficiency of a zero knowledge virtual machine is to use specialized components for complex operations and have the main circuit “offload” those operations to the corresponding components by specifying inputs and outputs and allowing the proof of execution to be done by the dedicated component instead of by the main circuit.
These specialized components are designed to prove the internal correctness of the execution of the operations they support. However, in isolation they cannot make any guarantees about the source of the input data or the destination of the output data.
In order to prove that the inputs and outputs specified by the main circuit match the inputs and outputs provably executed in the specialized component, some kind of provable communication bus is needed.
This bus is typically implemented as some kind of lookup argument, and in Miden VM in particular we use multiset checks.
bus can be implemented as a single trace column where a request can be sent to a specific component and a corresponding response will be sent back by that component.
The values in this column contain a running product of the communication with the component as follows:
- Each request is “sent” by computing a lookup value from some information that's specific to the specialized component, the operation inputs, and the operation outputs, and then dividing it out of the running product column .
- Each chiplet response is “sent” by computing the same lookup value from the component-specific information, inputs, and outputs, and then multiplying it into the running product column .
Thus, if the requests and responses match, and the bus column is initialized to , then will start and end with the value . This condition is enforced by boundary constraints on column .
Note that the order of the requests and responses does not matter, as long as they are all included in . In fact, requests and responses for the same operation will generally occur at different cycles. Additionally, there could be multiple requests sent in the same cycle, and there could also be a response provided at the same cycle that a request is received.
Communication bus constraints
These constraints can be expressed in a general way with the 2 following requirements:
- The lookup value must be computed using random values , etc. that are provided by the verifier after the prover has committed to the main execution trace.
- The lookup value must include all uniquely identifying information for the component/operation and its inputs and outputs.
Given an example operation with inputs and outputs , the lookup value can be computed as follows:
The constraint for sending this to the bus as a request would be:
The constraint for sending this to the bus as a response would be:
However, these constraints must be combined, since it's possible that requests and responses both occur during the same cycle.
To combine them, let be the request value and let be the response value. These values are both computed the same way as shown above, but the data sources are different, since the input/output values used to compute come from the trace of the component that's "offloading" the computation, while the input/output values used to compute come from the trace of the specialized component.
The final constraint can be expressed as:
Communication buses in Miden VM
In Miden VM, the specialized components are implemented as dedicated segments of the execution trace, which include the range checker and the 3 chiplets in the Chiplets module (the hash chiplet, bitwise chiplet, and memory chiplet).
Miden VM currently uses 2 buses to communicate with these components: