arrabbiata/
interpreter.rs

1//! This module contains the implementation of the IVC scheme in addition to
2//! running an arbitrary function that can use up to [crate::NUMBER_OF_COLUMNS]
3//! columns.
4//! At the moment, all constraints must be of maximum degree
5//! [crate::MAX_DEGREE], but it might change in the future.
6//!
7//! The implementation relies on a representation of the circuit as a 2D array
8//! of "data points" the interpreter can use.
9//!
10//! An interpreter defines what a "position" is in the circuit and allow to
11//! perform operations using these positions.
12//! Some of these positions will be considered as public inputs and might be
13//! fixed at setup time while making a proof, when other will be considered as
14//! private inputs.
15//!
16//! On top of these abstraction, gadgets are implemented.
17//! For the Nova-like IVC schemes, we describe below the different gadgets and
18//! how they are implemented with this abstraction.
19//!
20//! **Table of contents**:
21//! - [Gadgets implemented](#gadgets-implemented)
22//!   - [Elliptic curve addition](#elliptic-curve-addition)
23//!     - [Gadget layout](#gadget-layout)
24//!   - [Hash - Poseidon](#hash---poseidon)
25//!     - [Gadget layout](#gadget-layout-1)
26//!   - [Elliptic curve scalar multiplication](#elliptic-curve-scalar-multiplication)
27//!     - [Gadget layout](#gadget-layout-2)
28//! - [Handle the combinaison of constraints](#handle-the-combinaison-of-constraints)
29//! - [Permutation argument](#permutation-argument)
30//! - [Fiat-Shamir challenges](#fiat-shamir-challenges)
31//! - [Folding](#folding)
32//! - [Message passing](#message-passing)
33//!
34//! ## Gadgets implemented
35//!
36//! ### Elliptic curve addition
37//!
38//! The Nova augmented circuit requires to perform elliptic curve operations, in
39//! particular additions and scalar multiplications.
40//!
41//! To reduce the number of operations, we consider the affine coordinates.
42//! As a reminder, here are the equations to compute the addition of two
43//! different points `P1 = (X1, Y1)` and `P2 = (X2, Y2)`. Let define `P3 = (X3,
44//! Y3) = P1 + P2`.
45//!
46//! ```text
47//! - λ = (Y1 - Y2) / (X1 - X2)
48//! - X3 = λ^2 - X1 - X2
49//! - Y3 = λ (X1 - X3) - Y1
50//! ```
51//!
52//! Therefore, the addition of elliptic curve points can be computed using the
53//! following degree-2 constraints
54//!
55//! ```text
56//! - Constraint 1: λ (X1 - X2) - Y1 + Y2 = 0
57//! - Constraint 2: X3 + X1 + X2 - λ^2 = 0
58//! - Constraint 3: Y3 - λ (X1 - X3) + Y1 = 0
59//! ```
60//!
61//! If the points are the same, the λ is computed as follows:
62//!
63//! ```text
64//! - λ = (3 X1^2 + a) / (2Y1)
65//! ```
66//!
67//! #### Gadget layout
68//!
69//! For given inputs (x1, y1) and (x2, y2), the layout will be as follow:
70//!
71//! ```text
72//! | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | C13 | C14 | C15 |
73//! | -- | -- | -- | -- | -- | -- | -- | -- | -- | --- | --- | --- | --- | --- | --- |
74//! | x1 | y1 | x2 | y2 | b0 | λ  | x3 | y3 |    |     |     |     |     |     |     |
75//! ```
76//!
77//! where `b0` is equal two `1` if the points are the same, and `0` otherwise.
78//!
79//! TBD/FIXME: supports negation and the infinity point.
80//!
81//! TBD/FIXME: the gadget layout might change when we will implement the
82//! permutation argument. The values `(x1, y1)` can be public inputs.
83//! The values `(x2, y2)` can be fetched from the permutation argument, and must
84//! be the output of the elliptic curve scaling.
85//!
86//! The gadget requires therefore 7 columns.
87//!
88//! ### Hash - Poseidon
89//!
90//! Hashing is a crucial part of the IVC scheme. The hash function the
91//! interpreter does use for the moment is an instance of the Poseidon hash
92//! function with a fixed state size of
93//! [crate::curve::PlonkSpongeConstants::SPONGE_WIDTH]. Increasing the
94//! state size can be considered as it would potentially optimize the
95//! number of rounds, and allow hashing more data on one row. We leave this for
96//! future works.
97//!
98//! A direct optimisation would be to use
99//! [Poseidon2](https://eprint.iacr.org/2023/323) as its performance on CPU is
100//! better, for the same security level and the same cost in circuit. We leave
101//! this for future works.
102//!
103//! For a first version, we consider an instance of the Poseidon hash function
104//! that is suitable for curves whose field size is around 256 bits.
105//! A security analysis for these curves give us a recommandation of 60 full
106//! rounds if we consider a 128-bit security level and a low-degree
107//! exponentiation of `5`, with only full rounds.
108//! In the near future, we will consider the partial rounds strategy to reduce
109//! the CPU cost. For a first version, we keep the full rounds strategy to keep
110//! the design simple.
111//!
112//! When applying the full/partial round strategy, an optimisation can be used,
113//! see [New Optimization techniques for PlonK's
114//! arithmetisation](https://eprint.iacr.org/2022/462). The techniques described
115//! in the paper can also be generalized to other constraints used in the
116//! interpreter, but we leave this for future works.
117//!
118//! #### Gadget layout
119//!
120//! We start with the assumption that [crate::NUMBER_OF_COLUMNS] columns are
121//! available for the whole circuit, and we can support constraints up to degree
122//! [crate::MAX_DEGREE].
123//! Therefore, we can compute 5 full rounds per row by using the "next row"
124//! (i.e. adding an evaluation point at ζω). An implementation is provided in
125//! the gadget [crate::column::Gadget::PoseidonFullRound] and
126//! [crate::column::Gadget::PoseidonSpongeAbsorb].
127//!
128//! The layout for the one using the "next row" is as follow (5 full rounds):
129//! ```text
130//! | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | C11 | C12 | C13 | C14 | C15 |
131//! | -- | -- | -- | -- | -- | -- | -- | -- | -- | --- | --- | --- | --- | --- | --- |
132//! | x  | y  | z  | a1 | a2 | a3 | b1 | b2 | b3 | c1  | c2  | c3  | d1  | d2  | d3  |
133//! | o1 | o2 | o2
134//! ```
135//! where (x, y, z) is the input of the current step, (o1, o2, o3) is the
136//! output, and the other values are intermediary values. And we have the
137//! following equalities:
138//! ```text
139//! (a1, a2, a3) = PoseidonRound(x, y, z)
140//! (b1, b2, b3) = PoseidonRound(a1, a2, a3)
141//! (c1, c2, c3) = PoseidonRound(b1, b2, b3)
142//! (d1, d2, d3) = PoseidonRound(c1, c2, c3)
143//! (o1, o2, o3) = PoseidonRound(d1, d2, d3)
144//! ```
145//!
146//! Round constants are passed as public inputs. As a
147//! reminder, public inputs are simply additional columns known by the prover
148//! and verifier.
149//! Also, the elements to absorb are added to the initial state at the beginning
150//! of the call of the Poseidon full hash. The elements to absorb are supposed
151//! to be passed as public inputs.
152//!
153//! ### Elliptic curve scalar multiplication
154//!
155//! The Nova-based IVC schemes require to perform scalar multiplications on
156//! elliptic curve points. The scalar multiplication is computed using the
157//! double-and-add algorithm.
158//!
159//! We will consider a basic implementation using the "next row". The
160//! accumulators will be saved on the "next row". The decomposition of the
161//! scalar will be incremental on each row.
162//! The scalar used for the scalar multiplication will be fetched using the
163//! permutation argument (FIXME: to be implemented).
164//! More than one bit can be decomposed at the same time, and we could reduce
165//! the number of rows.
166//! We leave this for future work.
167//!
168//! #### Gadget layout
169//!
170//! For a (x, y) point and a scalar, we apply the double-and-add algorithm, one
171//! step per row.
172//!
173//! Therefore, we have 255 rows to compute the scalar multiplication.
174//! For a given step `i`, we have the following values:
175//! - `tmp_x`, `tmp_y`: the temporary values used to keep the double.
176//! - `res_x`, `res_y`: the result of the scalar multiplication i.e. the accumulator.
177//! - `b`: the i-th bit of the scalar.
178//! - `r_i` and `r_(i+1)`: scalars such that r_(i+1) = b + 2 * r_i.
179//! - `λ'` and `λ`: the coefficients
180//! - o'_x and o'_y equal to `res_plus_tmp_x` and `res_plus_tmp_y` if `b == 1`,
181//!   otherwise equal to `o_x` and `o_y`.
182//!
183//! We have the following layout:
184//!
185//! ```text
186//! | C1   |   C2   |      C3       |      C4       |    C5     | C7 |       C7       |       C8       | C9 | C10 |   C11    | C12 | C13 | C14 | C15 |
187//! | --   | -----  | ------------- | ------------- | --------- | -- | -------------- | -------------- | -- | --- | -------- | --- | --- | --- | --- |
188//! | o_x  |  o_y   | double_tmp_x  | double_tmp_y  |    r_i    | λ  | res_plus_tmp_x | res_plus_tmp_y | λ' |  b  |
189//! | o'_x |  o'_y  | double_tmp'_x | double_tmp'_y |  r_(i+1)  |
190//! ```
191//!
192//! FIXME: an optimisation can be implemented using "a bucket" style algorithm,
193//! as described in [Efficient MSMs in Kimchi
194//! Circuits](https://github.com/o1-labs/rfcs/blob/main/0013-efficient-msms-for-non-native-pickles-verification.md).
195//! We leave this for future work.
196//!
197//! ## Handle the combinaison of constraints
198//!
199//! The prover will have to combine the constraints to generate the
200//! full circuit at the end. The constraints will be combined using a
201//! challenge (often called α) that will be generated in the verifier circuit by
202//! simulating the Fiat-Shamir transformation.
203//! The challenges will then be accumulated over time using the random coin used
204//! by the folding argument.
205//! The verifier circuit must be carefully implemented to ensure that all the
206//! messages that the prover would have sent before coining the random combiner
207//! for the constraints has been absorbed properly in the verifier circuit.
208//!
209//! Using this technique requires us a folding scheme that handles degree
210//! `5 + 1` constraints, as the challenge will be considered as a variable.
211//! The reader can refer to this [HackMD
212//! document](https://hackmd.io/@dannywillems/Syo5MBq90) for more details.
213//!
214//! ## Permutation argument
215//!
216//! Communication between rows must be done using a permutation argument. The
217//! argument we use will be a generalisation of the one used in the [PlonK
218//! paper](https://eprint.iacr.org/2019/953).
219//!
220//! The construction of the permutations will be done using the methods prefixed
221//! `save` and `load`. The index of the current row and the index of the
222//! time the value has been written will be used to generate the permutation on
223//! the fly.
224//!
225//! The permutation argument described in the PlonK paper is a kind of "inverse
226//! lookup" protocol, like Plookup. The polynomials are defined as follows:
227//!
228//! ```text
229//!            Can be seen as T[f(X)] = Χ
230//!          --------
231//!          |      |
232//! f'(X) = f(X) + β X + γ
233//!                      |--- Can be seen as the evaluation point.
234//!                         |
235//!                         |
236//! g'(X) = g(X) + β σ(X) + γ
237//!          |      |
238//!          --------
239//!          Can be seen as T[g(X)] = σ(X)
240//! ```
241//!
242//! And from this, we build an accumulator, like for Plookup.
243//! The accumulator requires to coin two challenges, β and γ, and it must be
244//! done after the commitments to the columns have been absorbed.
245//! The verifier at the next step will verify that the challenges have been
246//! correctly computed.
247//! In the implementation, the accumulator will be computed after the challenges
248//! and the commitments. Note that the accumulator must also be aggregated, and
249//! the aggregation must be performed by the verifier at the next step.
250//!
251//! The methods `save` and `load` will accept as arguments only a column that is
252//! included in the permutation argument. For instance, `save_poseidon_state`
253//! will only accept columns with index 3, 4 and 5, where the
254//! `load_poseidon_state` will only accepts columns with index 0, 1 and 2.
255//!
256//! The permutations values will be saved in public values, and will contain the
257//! index of the row. The permutation values will be encoded with a 32 bits
258//! value (u32) as we can suppose a negligible probability that a user will use
259//! more than 2^32 rows.
260//!
261//! The permutation argument also generates constraints that will be
262//! homogenized with the gadget constraints.
263//!
264//! Note all rows might require to use the permutation argument. Therefore, a
265//! selector will be added to activate/deactivate the permutation argument.
266//! When a method calls `save` or `load`, the selector will be activated. By
267//! default, the selector will be deactivated.
268//!
269//! TBD:
270//! - number of columns
271//! - accumulator column
272//! - folding of the permutation argument
273//!
274//! TBD/FIXME: do we use a additive permutation argument to increase the number
275//! of columns we can perform the permutation on?
276//!
277//! TBD/FIXME: We can have more than one permutation argument. For instance, we
278//! can have a permutation argument for the columns 0, 1, 2, 3 and one for the
279//! columns 4, 5, 6, 7. It can help to decrease the degree.
280//!
281//! ## Fiat-Shamir challenges
282//!
283//! The challenges sent by the verifier must also be simulated by the verifier
284//! circuit. It is done by passing "messages" as public inputs to the next
285//! instances. Diagrams recapitulating the messages that must be passed are
286//! available in the section [Message passing](#message-passing).
287//!
288//! For a step `i + 1`, the challenges of the step `i` must be computed by the
289//! verifier in the circuit, and check that it corresponds to the ones received
290//! as a public input.
291//!
292//! TBD/FIXME: specify. Might require foreign field arithmetic.
293//!
294//! TBD/FIXME: do we need to aggregate them for the end?
295//!
296//! ## Folding
297//!
298//! Constraints must be homogenized for the folding scheme.
299//! Homogenising a constraint means that we add a new variable (called "U" in
300//! Nova for instance) that will be used to homogenize the degree of the monomials
301//! forming the constraint.
302//! Next to this, additional information, like the cross-terms and the error
303//! terms must be computed.
304//!
305//! This computation depends on the constraints, and in particular on the
306//! monomials describing the constraints.
307//! The computation of the cross-terms and the error terms happen after the
308//! witness has been built and the different arguments like the permutation and
309//! lookups have been done. Therefore, the interpreter must provide a method to
310//! compute it, and the constraints should be passed as an argument.
311//!
312//! When computing the cross-terms, we must compute the contribution of each
313//! monomial to it.
314//!
315//! The implementation works as follow:
316//! - Split the constraint in monomials
317//! - For the monomials of degree `d`, compute the contribution when
318//!   homogenizing to degree `d'`.
319//! - Sum all the contributions.
320//!
321//! The library [mvpoly] can be used to compute the cross-terms and to
322//! homogenize the constraints. The constraints can be converted into a type
323//! implementing the trait [MVPoly](mvpoly::MVPoly) and the method
324//! [compute_cross_terms](mvpoly::MVPoly::compute_cross_terms) can be used from
325//! there.
326//!
327//! ## Message passing
328//!
329//! The message passing is a crucial part of the IVC scheme. The messages are
330//! mostly commitments and challenges that must be passed from one step to
331//! another, and by "jumping" between curves. In the implementation,
332//! the messages are kept in an "application environment", located in the
333//! "witness environment". The structure [crate::witness::Env] is used to keep
334//! track of the messages that must be passed.
335//! Each step starts with an "program state" and end with another that is
336//! accumulated. The final state is passed through a "digest" to the next
337//! instance. The digest is performed using a hash function (see [Hash -
338//! Poseidon](#hash---poseidon)). We often use the term "sponge" to refer to the
339//! hash function or the state of the hash function.
340//!
341//! The sponge state will be forked at different steps, to provide a
342//! consistent state between the different instances and the different curves.
343//! The challenges will be computed using the sponge state after hashing the
344//! appropriate values from the appropriate initial state, and be kept in the
345//! environment.
346//!
347//! In the diagram below, each object subscripted by `(p, n)` (resp. `(q, n)`)
348//! means that they are related to the instance `n` whose circuit is defined in
349//! the field `Fp` (resp. `Fq`), the scalar field of Vesta (resp. Pallas).
350//!
351//! In addition to that, we use
352//! - `w_(p, n)` for the witness.
353//! - `W_(p, n)` for the aggregated witness.
354//! - `C_(p, n)` for the commitment to the witness `w_(p, n)`.
355//! - `acc_(p, n)` for the accumulated commitments to the aggregated witness
356//!    `W_(p, n)`.
357//! - `α_(p, n)` for the challenge used to combine constraints.
358//! - `β_(p, n)` and `γ_(p, n)` for the challenge used to for the
359//!   permutation argument.
360//! - `r_(p, n)` for the challenge used for the accumulation of the
361//! - `t_(p, n, i)` for the evaluations of the cross-terms of degree `i`.
362//! - `Ct_(p, n, i)` for the commitments to the cross-terms of degree `i`.
363//!    witness/commitments.
364//! - `u_(p, n)` for the challenge used to homogenize the constraints.
365//! - `o_(p, n)` for the final digest of the sponge state.
366//!
367//! Here a diagram (FIXME: this is not complete) that shows the messages that
368//! must be passed:
369//!
370//! ```text
371//! +------------------------------------------+                      +------------------------+
372//! |            Instance n                    |                      |     Instance n + 2     |
373//! |        (witness w_(p, n))                |                      | (witness W_(p, n + 1)) |
374//! |            ----------                    |                      |     ----------         |
375//! |               Vesta                      |                      |         Vesta          |
376//! |         (scalar field = Fp)              |                      |   (scalar field = Fp)  |
377//! |         (base field   = Fq)              |                      |   (base field   = Fq)  |
378//! |          (Sponge over Fq)                |                      |    (Sponge over Fq)    |
379//! |                                          |                      |                        |
380//! |          Generate as output              |                      |   Generate as output   |
381//! |          ------------------              |                      |   ------------------   |
382//! | Challenges:                              |                      | Challenges:            |
383//! | - α_(p, n)                               |                      | - α_(p, n + 1)         |
384//! | - β_(p, n)                               |                      | - β_(p, n + 1)         |
385//! | - γ_(p, n)                               |                      | - γ_(p, n + 1)         |
386//! | - r_(p, n)                               |                      | - r_(p, n + 1)         |
387//! | - u_(p, n)                               |                      | - u_(p, n + 1)         |
388//! | Commitments:                             |                      |          (...)         |
389//! | - Cross-terms (`Ct_(p, n)`)              |                      +------------------------+
390//! | - Witness columns (`w_(p, n)`)           |                                   ^
391//! | Fiat-Shamir:                             |                                   |
392//! | - digest of all messages read until      |                                   |
393//! | now -> `o_(p, n)`                        |                                   |
394//! |                                          |                                   |
395//! |             Receive in PI                |                                   |
396//! |             --------------               |                                   |
397//! | - Commitments to w_(p, (n - 1))          |                                   |
398//! | - Final digest of the program            |                                   |
399//! | state at instance (n - 1)                |                                   |
400//! | (o_(q, n - 1)).                          |                                   |
401//! | - Final digest of the program            |                                   |
402//! | state at instance (n - 2)                |                                   |
403//! | (o_(p, n - 1)).                          |                                   |
404//! |                                          |                                   |
405//! |             Responsibility               |                                   |
406//! |             --------------               |                                   |
407//! | - Verify FS challenges (α_(q, n - 1),    |                                   |
408//! | β_(q, n - 1), γ_(q, n - 1),              |                                   |
409//! | r_(q, n - 1), u_(q, n - 1)               |                                   |
410//! | (i.e. challenges of instance n - 1)      |                                   |
411//! | from o_(q, n - 1)                        |                                   |
412//! | - Aggregate witness columns w_(p, n)     |                                   |
413//! | into W_(p, n).                           |                                   |
414//! | - Aggregate error terms                  |                                   |
415//! | from instance n - 2 with cross terms of  |                                   |
416//! | instance n - 2                           |                                   |
417//! +------------------------------------------+                                   |
418//!                      |                                                         |
419//!                      |                                                         |
420//!                      |             +----------------------------+              |
421//!                      |             |       Instance n + 1       |              |
422//!                      |             |   (witness w_(q, n))       |              |
423//!                      |             |       --------------       |              |
424//!                      |             |           Pallas           |              |
425//!                      |             |      (scalar field = Fq)   |              |
426//!                      |-----------> |      (base field   = Fp)   |--------------
427//!                                    |       (Sponge over Fp)     |
428//! TODO: define msgs                  |                            |    TODO: define msgs
429//!       format to pass               |            TODO            |     format to pass
430//!         IO                         |                            |          IO
431//!                                    |                            |
432//!                                    |                            |
433//!                                    +----------------------------+
434//! ```
435//!
436//! ### Workflow example for handling a challenge
437//!
438//! Handling challenges is performed in two steps. Let's take the example of the
439//! verifier coin `u` used to homogenize the constraints/polynomials in the
440//! accumulation protocol. For the sake of simplicity, we only focus on the work
441//! related to the challenges. In addition to that, a verifier coin `r` is used
442//! to accumulate the challenges. Therefore, we start with the following
443//! diagram:
444//!
445//! ```text
446//! +------------------------------------------+
447//! |            Instance n                    |
448//! |        (witness w_(p, n))                |
449//! |            ----------                    |
450//! |               Vesta                      |
451//! |         (scalar field = Fp)              |
452//! |         (base field   = Fq)              |
453//! |          (Sponge over Fq)                |
454//! |                                          |
455//! | Generate as output:                      |
456//! | - u_(p, n)                               |
457//! | - r                                      |
458//! | - "accumulated u": u_p + r * u_(p, n)    |
459//! |     (note the operations are over Fp)    |
460//! +------------------------------------------+
461//! ```
462//!
463//! The coins `u_(p, n)` and `r` are generated after absorbing a few committed
464//! values (i.e. points over Fq). The verifier will have to check the following:
465//! - `u_(p, n)` has been coined correctly (i.e. check a sponge state).
466//! - `r` has been coined correctly (i.e. check a sponge staet).
467//! - "accumulated u" has been computed correctly
468//!
469//! At the next iteration, the verifier is working over the field `Fq`, i.e. the
470//! field that is used to generate the challenges. Therefore, it can perform the
471//! first two checks. We have then the following diagram.
472//!
473//! ```text
474//! +------------------------------------------+
475//! |            Instance n                    |
476//! |        (witness w_(p, n))                |
477//! |            ----------                    |
478//! |               Vesta                      |
479//! |         (scalar field = Fp)              |
480//! |         (base field   = Fq)              |
481//! |          (Sponge over Fq)                |
482//! |                                          |
483//! | Generate as output:                      |
484//! | - u_(p, n)                               |
485//! | - r                                      |
486//! | - "accumulated u": u_p + r * u_(p, n)    |
487//! |     (note the operations are over Fp)    |
488//! +------------------------------------------+
489//!       |
490//!       |
491//!       |
492//!       |               +-----------------------------+
493//!       |               |       Instance (n + 1)      |
494//!       |               |      (witness w_(q, n))     |
495//!       |-------------> |          ----------         |
496//!                       |          Pallas             |
497//!                       |    (scalar field = Fq)      |
498//!                       |    (base field   = Fp)      |
499//!                       |     (Sponge over Fp)        |
500//!                       |                             |
501//!                       |     Receive as (public)     |
502//!                       |           inputs            |
503//!                       |       ---------------       |
504//!                       | - commitments generated     |
505//!                       | by instance n (Fq elements) |
506//!                       | - u_(p, n)                  |
507//!                       | - r                         |
508//!                       | - (more but unused by       |
509//!                       | the verifier)               |
510//!                       |                             |
511//!                       |      Verifier circuit       |
512//!                       |      ----------------       |
513//!                       | - run the sponge to         |
514//!                       | check the value u_(p, n)    |
515//!                       | and r                       |
516//!                       +-----------------------------+
517//! ```
518//!
519//! The last check, i.e. checking the accumulation of `u`, is "delayed" for the
520//! instance (n + 2), to be able to perform the accumulation over Fp.
521//!
522//! Therefore, we end up with the following diagram:
523//!
524//! ```text
525//! +------------------------------------------+            +-------------------------------------+
526//! |            Instance n                    |            |            Instance (n + 2)         |
527//! |        (witness w_(p, n))                |            |         (witness w_(p, n + 1))      |
528//! |            ----------                    |            |            ----------               |
529//! |               Vesta                      |            |               Vesta                 |
530//! |         (scalar field = Fp)              |            |         (scalar field = Fp)         |
531//! |         (base field   = Fq)              |            |         (base field   = Fq)         |
532//! |          (Sponge over Fq)                |            |          (Sponge over Fq)           |
533//! |                                          |            |                                     |
534//!            Generate as output:             |            |        Receive as (public inputs)   |
535//!              ---------------               |            |           -------------------       |
536//! | - u_(p, n)                               |            | - `u_(p, n)`                        |
537//! | - r                                      |            | - accumulated u, `acc_u`            |
538//! | - "accumulated u": u_p + r * u_(p, n)    |            | - random coin `r`                   |
539//! |     (note the operations are over Fp)    |            | - "old accumulated value" u_p       |
540//! +------------------------------------------+            |           Verifier circuit          |
541//!       |                                                 |           ----------------          |
542//!       |                                                 | - check that                        |
543//!       |                                                 | `acc_u = u_p + r * u_(p, n)`        |
544//!       |                                                 +-------------------------------------+
545//!       |                                                                 ^
546//!       |                                                                 |
547//!       |                                                                 |
548//!       |               +-----------------------------+                   |
549//!       |               |       Instance (n + 1)      |                   |
550//!       |               |      (witness w_(q, n))     |                   |
551//!       |-------------> |          ----------         |  ------------------
552//!                       |          Pallas             |
553//!                       |    (scalar field = Fq)      |
554//!                       |    (base field   = Fp)      |
555//!                       |     (Sponge over Fp)        |
556//!                       |                             |
557//!                       |     Receive as (public)     |
558//!                       |           inputs            |
559//!                       |       ---------------       |
560//!                       | - commitments generated     |
561//!                       | by instance n (Fq elements) |
562//!                       | - u_(p, n)                  |
563//!                       | - r                         |
564//!                       | - (more but unused by       |
565//!                       | the verifier)               |
566//!                       |                             |
567//!                       |      Verifier circuit       |
568//!                       |      ----------------       |
569//!                       | - run the sponge to         |
570//!                       | check the value u_(p, n)    |
571//!                       | and r                       |
572//!                       +-----------------------------+
573//! ```
574
575use crate::{curve::PlonkSpongeConstants, MAXIMUM_FIELD_SIZE_IN_BITS, NUMBER_OF_COLUMNS};
576use ark_ff::{One, Zero};
577use log::debug;
578use mina_poseidon::constants::SpongeConstants;
579use num_bigint::BigInt;
580
581/// A list of instruction/gadget implemented in the interpreter.
582/// The control flow can be managed by implementing a function
583/// `fetch_next_instruction` and `fetch_instruction` on a witness environnement.
584/// See the [Witness environment](crate::witness::Env) for more details.
585///
586/// Mostly, the instructions will be used to build the verifier circuit, but it
587/// can be generalized.
588///
589/// When the circuit is predefined, the instructions can be accompanied by a
590/// public selector. When implementing a virtual machine, where instructions are
591/// unknown at compile time, other methods can be used. We leave this for future
592/// work.
593///
594/// For the moment, the type is not parametrized, on purpose, to keep it simple
595/// (KISS method). However, IO could be encoded in the type, and encode a
596/// typed control-flow. We leave this for future work.
597#[derive(Copy, Clone, Debug)]
598pub enum Instruction {
599    /// This gadget implement the Poseidon hash instance described in the
600    /// top-level documentation. In the current setup, with [NUMBER_OF_COLUMNS]
601    /// columns, we can compute 5 full rounds per row.
602    ///
603    /// We split the Poseidon gadget in 13 sub-gadgets, one for each set of 5
604    /// full rounds and one for the absorbtion. The parameter is the starting
605    /// round of the permutation. It is expected to be a multiple of five.
606    ///
607    /// Note that, for now, the gadget can only be used by the verifier circuit.
608    PoseidonFullRound(usize),
609    /// Absorb [PlonkSpongeConstants::SPONGE_WIDTH - 1] elements into the
610    /// sponge. The elements are absorbed into the last
611    /// [PlonkSpongeConstants::SPONGE_WIDTH - 1] elements of the sponge state.
612    ///
613    /// The values to be absorbed depend on the state of the environment while
614    /// executing this instruction.
615    ///
616    /// Note that, for now, the gadget can only be used by the verifier circuit.
617    PoseidonSpongeAbsorb,
618    EllipticCurveScaling(usize, u64),
619    EllipticCurveAddition(usize),
620    // The NoOp will simply do nothing
621    NoOp,
622}
623
624/// The first instruction in the verifier circuit (often shortened in "IVC" in
625/// the crate) is the Poseidon permutation. It is used to start hashing the
626/// public input.
627pub const VERIFIER_STARTING_INSTRUCTION: Instruction = Instruction::PoseidonSpongeAbsorb;
628
629/// Define the side of the temporary accumulator.
630/// When computing G1 + G2, the interpreter will load G1 and after that G2.
631/// This enum is used to decide which side fetching into the cells.
632/// In the near future, it can be replaced by an index.
633pub enum Side {
634    Left,
635    Right,
636}
637
638/// An abstract interpreter that provides some functionality on the circuit. The
639/// interpreter should be seen as a state machine with some built-in
640/// functionality whose state is a matrix, and whose transitions are described
641/// by polynomial functions.
642pub trait InterpreterEnv {
643    type Position: Clone + Copy;
644
645    /// The variable should be seen as a certain object that can be built by
646    /// multiplying and adding, i.e. the variable can be seen as a solution
647    /// to a polynomial.
648    /// When instantiating as expressions - "constraints" - it defines
649    /// multivariate polynomials.
650    type Variable: Clone
651        + core::ops::Add<Self::Variable, Output = Self::Variable>
652        + core::ops::Sub<Self::Variable, Output = Self::Variable>
653        + core::ops::Mul<Self::Variable, Output = Self::Variable>
654        + core::fmt::Debug
655        + Zero
656        + One;
657
658    /// Allocate a new variable in the circuit for the current row
659    fn allocate(&mut self) -> Self::Position;
660
661    /// Allocate a new variable in the circuit for the next row
662    fn allocate_next_row(&mut self) -> Self::Position;
663
664    /// Return the corresponding variable at the given position
665    fn read_position(&self, pos: Self::Position) -> Self::Variable;
666
667    /// Set the value of the variable at the given position for the current row
668    fn write_column(&mut self, col: Self::Position, v: Self::Variable) -> Self::Variable;
669
670    /// Build the constant zero
671    fn zero(&self) -> Self::Variable;
672
673    /// Build the constant one
674    fn one(&self) -> Self::Variable;
675
676    fn constant(&self, v: BigInt) -> Self::Variable;
677
678    /// Assert that the variable is zero
679    fn assert_zero(&mut self, x: Self::Variable);
680
681    /// Assert that the two variables are equal
682    fn assert_equal(&mut self, x: Self::Variable, y: Self::Variable);
683
684    fn add_constraint(&mut self, x: Self::Variable);
685
686    fn constrain_boolean(&mut self, x: Self::Variable);
687
688    /// Compute the square a field element
689    fn square(&mut self, res: Self::Position, x: Self::Variable) -> Self::Variable;
690
691    /// Flagged as unsafe as it does require an additional range check
692    ///
693    /// # Safety
694    ///
695    /// There are no constraints on the returned value; callers must assert the
696    /// relationship with the source variable `x` and that the returned value
697    /// fits in `highest_bit - lowest_bit` bits.
698    unsafe fn bitmask_be(
699        &mut self,
700        x: &Self::Variable,
701        highest_bit: u32,
702        lowest_bit: u32,
703        position: Self::Position,
704    ) -> Self::Variable;
705
706    /// Fetch an input of the application
707    // Witness-only
708    fn fetch_input(&mut self, res: Self::Position) -> Self::Variable;
709
710    /// Reset the environment to build the next row
711    fn reset(&mut self);
712
713    /// Return the folding combiner
714    fn coin_folding_combiner(&mut self, pos: Self::Position) -> Self::Variable;
715
716    /// Compute the x^5 of the given variable
717    fn compute_x5(&self, x: Self::Variable) -> Self::Variable {
718        let x_square = x.clone() * x.clone();
719        let x_cubed = x_square.clone() * x.clone();
720        x_cubed * x_square.clone()
721    }
722
723    // ---- Poseidon gadget -----
724    /// Load the state of the Poseidon hash function into the environment
725    fn load_poseidon_state(&mut self, pos: Self::Position, i: usize) -> Self::Variable;
726
727    /// Save the state of poseidon into the environment
728    ///
729    /// # Safety
730    ///
731    /// It does not have any effect on the constraints
732    unsafe fn save_poseidon_state(&mut self, v: Self::Variable, i: usize);
733
734    /// Return the Poseidon round constants as a constant.
735    fn get_poseidon_round_constant(&self, round: usize, i: usize) -> Self::Variable;
736
737    /// Return the requested MDS matrix coefficient
738    fn get_poseidon_mds_matrix(&mut self, i: usize, j: usize) -> Self::Variable;
739
740    /// Load the value to absorb at the current step at the position given by
741    /// `pos`.
742    ///
743    /// The values and the absorption order is defined by the
744    /// state of the environment itself.
745    ///
746    /// IMPROVEME: we could have in the environment an heterogeneous typed list,
747    /// and we pop values call after call. However, we try to keep the
748    /// interpreter simple.
749    ///
750    /// # Safety
751    ///
752    /// No constraint is added. It should be used with caution.
753    unsafe fn fetch_value_to_absorb(&mut self, pos: Self::Position) -> Self::Variable;
754    // -------------------------
755
756    /// Check if the points given by (x1, y1) and (x2, y2) are equals.
757    ///
758    /// # Safety
759    ///
760    /// No constraint is added. It should be used with caution.
761    unsafe fn is_same_ec_point(
762        &mut self,
763        pos: Self::Position,
764        x1: Self::Variable,
765        y1: Self::Variable,
766        x2: Self::Variable,
767        y2: Self::Variable,
768    ) -> Self::Variable;
769
770    /// Inverse of a variable
771    ///
772    /// # Safety
773    ///
774    /// Zero is not allowed as an input.
775    /// Witness only
776    // IMPROVEME: when computing the witness, we could have an additional column
777    // that would stand for the inverse, and compute all inverses at the end
778    // using a batch inversion.
779    unsafe fn inverse(&mut self, pos: Self::Position, x: Self::Variable) -> Self::Variable;
780
781    /// Compute the coefficient λ used in the elliptic curve addition.
782    /// If the two points are the same, the λ is computed as follows:
783    /// - λ = (3 X1^2 + a) / (2Y1)
784    ///
785    /// Otherwise, the λ is computed as follows:
786    /// - λ = (Y1 - Y2) / (X1 - X2)
787    fn compute_lambda(
788        &mut self,
789        pos: Self::Position,
790        is_same_point: Self::Variable,
791        x1: Self::Variable,
792        y1: Self::Variable,
793        x2: Self::Variable,
794        y2: Self::Variable,
795    ) -> Self::Variable;
796
797    /// Double the elliptic curve point given by the affine coordinates
798    /// `(x1, y1)` and save the result in the registers `pos_x` and `pos_y`.
799    /// The last argument, `row`, is used to decide if the result should be
800    /// written in the current or the next row of the variable position `pos_x`
801    /// and `pos_y`.
802    fn double_ec_point(
803        &mut self,
804        pos_x: Self::Position,
805        pos_y: Self::Position,
806        x1: Self::Variable,
807        y1: Self::Variable,
808    ) -> (Self::Variable, Self::Variable);
809
810    /// Load the affine coordinates of the elliptic curve point currently saved
811    /// in the temporary accumulators. Temporary accumulators could be seen as
812    /// a CPU cache, an intermediate storage between the RAM (random access
813    /// memory) and the CPU registers (memory cells that are constrained).
814    ///
815    /// For now, it can only be used to load affine coordinates of elliptic
816    /// curve points given in the short Weierstrass form.
817    ///
818    /// Temporary accumulators could also be seen as return values of a function.
819    ///
820    /// # Safety
821    ///
822    /// No constraints are enforced. It is not also enforced that the
823    /// accumulators have been cleaned between two different gadgets.
824    unsafe fn load_temporary_accumulators(
825        &mut self,
826        pos_x: Self::Position,
827        pos_y: Self::Position,
828        side: Side,
829    ) -> (Self::Variable, Self::Variable);
830
831    /// Save temporary accumulators into the environment
832    ///
833    /// # Safety
834    ///
835    /// It does not have any effect on the constraints.
836    unsafe fn save_temporary_accumulators(
837        &mut self,
838        _v1: Self::Variable,
839        _v2: Self::Variable,
840        _side: Side,
841    );
842}
843
844/// Run the application
845pub fn run_app<E: InterpreterEnv>(env: &mut E) {
846    let x1 = {
847        let pos = env.allocate();
848        env.fetch_input(pos)
849    };
850    let _x1_square = {
851        let res = env.allocate();
852        env.square(res, x1.clone())
853    };
854}
855
856/// Run an iteration of the IVC scheme
857///
858/// It consists of the following steps:
859/// 1. Compute the hash of the public input.
860/// 2. Compute the elliptic curve addition.
861/// 3. Run the polynomial-time function.
862/// 4. Compute the hash of the output.
863///
864/// The environment is updated over time.
865/// When the environment is the one described in the [Witness
866/// environment](crate::witness::Env), the structure will be updated
867/// with the new accumulator, the new public input, etc. The public output will
868/// be in the structure also. The user can simply rerun the function for the
869/// next iteration.
870///
871/// A row must be created to generate a challenge to combine the constraints
872/// later. The challenge will be also accumulated over time.
873///
874/// FIXME: the resulting constraints do not include the selectors, yet. The
875/// resulting constraints must be multiplied by the corresponding selectors.
876pub fn run_ivc<E: InterpreterEnv>(env: &mut E, instr: Instruction) {
877    match instr {
878        Instruction::EllipticCurveScaling(i_comm, processing_bit) => {
879            assert!(processing_bit < MAXIMUM_FIELD_SIZE_IN_BITS, "Invalid bit index. The fields are maximum on {MAXIMUM_FIELD_SIZE_IN_BITS} bits, therefore we cannot process the bit {processing_bit}");
880            assert!(i_comm < NUMBER_OF_COLUMNS, "Invalid index. We do only support the scaling of the commitments to the columns, for now. We must additionally support the scaling of cross-terms and error terms");
881            debug!("Processing scaling of commitment {i_comm}, bit {processing_bit}");
882            // When processing the first bit, we must load the scalar, and it
883            // comes from previous computation.
884            // The two first columns are supposed to be used for the output.
885            // It will be used to write the result of the scalar multiplication
886            // in the next row.
887            let res_col_x = env.allocate();
888            let res_col_y = env.allocate();
889            let tmp_col_x = env.allocate();
890            let tmp_col_y = env.allocate();
891            let scalar_col = env.allocate();
892            let next_row_res_col_x = env.allocate_next_row();
893            let next_row_res_col_y = env.allocate_next_row();
894            let next_row_tmp_col_x = env.allocate_next_row();
895            let next_row_tmp_col_y = env.allocate_next_row();
896            let next_row_scalar_col = env.allocate_next_row();
897
898            // For the bit, we do have two cases. If we are processing the first
899            // bit, we must load the bit from a previous computed value. In the
900            // case of folding, it will be the output of the Poseidon hash.
901            // Therefore we do need the permutation argument.
902            // If it is not the first bit, we suppose the previous value has
903            // been written in the previous step in the current row.
904            let scalar = if processing_bit == 0 {
905                env.coin_folding_combiner(scalar_col)
906            } else {
907                env.read_position(scalar_col)
908            };
909            // FIXME: we do add the blinder. We must substract it at the end.
910            // Perform the following algorithm (double-and-add):
911            // res = O <-- blinder
912            // tmp = P
913            // for i in 0..256:
914            //   if r[i] == 1:
915            //     res = res + tmp
916            //   tmp = tmp + tmp
917            //
918            // - `processing_bit` is the i-th bit of the scalar, i.e. i in the loop
919            // described above.
920            // - `i_comm` is used to fetch the commitment.
921            //
922            // If it is the first bit, we must load the commitment using the
923            // permutation argument.
924            // FIXME: the initial value of the result should be a non-zero
925            // point. However, it must be a public value otherwise the prover
926            // might lie on the initial value.
927            // We do have 15 public inputs on each row, we could use some of them.
928            let (res_x, res_y) = if processing_bit == 0 {
929                // Load the commitment
930                unsafe { env.load_temporary_accumulators(res_col_x, res_col_y, Side::Right) }
931            } else {
932                // Otherwise, the previous step has written the previous result
933                // in its "next row", i.e. this row.
934                let res_x = { env.read_position(res_col_x) };
935                let res_y = { env.read_position(res_col_y) };
936                (res_x, res_y)
937            };
938            // Same for the accumulated temporary value
939            let (tmp_x, tmp_y) = if processing_bit == 0 {
940                unsafe { env.load_temporary_accumulators(tmp_col_x, tmp_col_y, Side::Left) }
941            } else {
942                (env.read_position(tmp_col_x), env.read_position(tmp_col_y))
943            };
944            // Conditional addition:
945            // if bit == 1, then res = tmp + res
946            // else res = res
947            // First we compute tmp + res
948            // FIXME: we do suppose that res != tmp -> no doubling and no check
949            // if they are the same
950            // IMPROVEME: reuse elliptic curve addition
951            let (res_plus_tmp_x, res_plus_tmp_y) = {
952                let lambda = {
953                    let pos = env.allocate();
954                    env.compute_lambda(
955                        pos,
956                        env.zero(),
957                        tmp_x.clone(),
958                        tmp_y.clone(),
959                        res_x.clone(),
960                        res_y.clone(),
961                    )
962                };
963                // x3 = λ^2 - x1 - x2
964                let x3 = {
965                    let pos = env.allocate();
966                    let lambda_square = lambda.clone() * lambda.clone();
967                    let res = lambda_square.clone() - tmp_x.clone() - res_x.clone();
968                    env.write_column(pos, res)
969                };
970                // y3 = λ (x1 - x3) - y1
971                let y3 = {
972                    let pos = env.allocate();
973                    let x1_minus_x3 = tmp_x.clone() - x3.clone();
974                    let res = lambda.clone() * x1_minus_x3.clone() - tmp_y.clone();
975                    env.write_column(pos, res)
976                };
977                (x3, y3)
978            };
979            // tmp = tmp + tmp
980            // Compute the double of the temporary value
981            // The slope is saved in a column created in the call to
982            // `double_ec_point`
983            // We ignore the result as it will be used at the next step only.
984            let (_double_tmp_x, _double_tmp_y) = {
985                env.double_ec_point(
986                    next_row_tmp_col_x,
987                    next_row_tmp_col_y,
988                    tmp_x.clone(),
989                    tmp_y.clone(),
990                )
991            };
992            let bit = {
993                let pos = env.allocate();
994                unsafe { env.bitmask_be(&scalar, 1, 0, pos) }
995            };
996            // Checking it is a boolean -> degree 2
997            env.constrain_boolean(bit.clone());
998            let next_scalar = {
999                unsafe {
1000                    env.bitmask_be(
1001                        &scalar,
1002                        MAXIMUM_FIELD_SIZE_IN_BITS.try_into().unwrap(),
1003                        1,
1004                        next_row_scalar_col,
1005                    )
1006                }
1007            };
1008            // Degree 1
1009            env.assert_equal(
1010                scalar.clone(),
1011                bit.clone() + env.constant(BigInt::from(2)) * next_scalar.clone(),
1012            );
1013            let _x3 = {
1014                let res = bit.clone() * res_plus_tmp_x.clone()
1015                    + (env.one() - bit.clone()) * res_x.clone();
1016                env.write_column(next_row_res_col_x, res)
1017            };
1018            let _y3 = {
1019                let res = bit.clone() * res_plus_tmp_y.clone()
1020                    + (env.one() - bit.clone()) * res_y.clone();
1021                env.write_column(next_row_res_col_y, res)
1022            };
1023        }
1024        Instruction::EllipticCurveAddition(i_comm) => {
1025            assert!(i_comm < NUMBER_OF_COLUMNS, "Invalid index. We do only support the addition of the commitments to the columns, for now. We must additionally support the scaling of cross-terms and error terms");
1026            let (x1, y1) = {
1027                let x1 = env.allocate();
1028                let y1 = env.allocate();
1029                unsafe { env.load_temporary_accumulators(x1, y1, Side::Left) }
1030            };
1031            let (x2, y2) = {
1032                let x2 = env.allocate();
1033                let y2 = env.allocate();
1034                unsafe { env.load_temporary_accumulators(x2, y2, Side::Right) }
1035            };
1036            let is_same_point = {
1037                let pos = env.allocate();
1038                unsafe { env.is_same_ec_point(pos, x1.clone(), y1.clone(), x2.clone(), y2.clone()) }
1039            };
1040            let lambda = {
1041                let pos = env.allocate();
1042                env.compute_lambda(
1043                    pos,
1044                    is_same_point,
1045                    x1.clone(),
1046                    y1.clone(),
1047                    x2.clone(),
1048                    y2.clone(),
1049                )
1050            };
1051            // x3 = λ^2 - x1 - x2
1052            let x3 = {
1053                let pos = env.allocate();
1054                let lambda_square = lambda.clone() * lambda.clone();
1055                let res = lambda_square.clone() - x1.clone() - x2.clone();
1056                env.write_column(pos, res)
1057            };
1058            // y3 = λ (x1 - x3) - y1
1059            {
1060                let pos = env.allocate();
1061                let x1_minus_x3 = x1.clone() - x3.clone();
1062                let res = lambda.clone() * x1_minus_x3.clone() - y1.clone();
1063                env.write_column(pos, res)
1064            };
1065        }
1066        Instruction::PoseidonFullRound(starting_round) => {
1067            assert!(
1068                starting_round < PlonkSpongeConstants::PERM_ROUNDS_FULL,
1069                "Invalid round index. Only values below {} are allowed.",
1070                PlonkSpongeConstants::PERM_ROUNDS_FULL
1071            );
1072            assert!(
1073                starting_round % 5 == 0,
1074                "Invalid round index. Only values that are multiple of 5 are allowed."
1075            );
1076            debug!(
1077                "Executing instruction Poseidon starting from round {starting_round} to {}",
1078                starting_round + 5
1079            );
1080
1081            let round_input_positions: Vec<E::Position> = (0..PlonkSpongeConstants::SPONGE_WIDTH)
1082                .map(|_i| env.allocate())
1083                .collect();
1084
1085            let round_output_positions: Vec<E::Position> = (0..PlonkSpongeConstants::SPONGE_WIDTH)
1086                .map(|_i| env.allocate_next_row())
1087                .collect();
1088
1089            let state: Vec<E::Variable> = if starting_round == 0 {
1090                round_input_positions
1091                    .iter()
1092                    .enumerate()
1093                    .map(|(i, pos)| env.load_poseidon_state(*pos, i))
1094                    .collect()
1095            } else {
1096                round_input_positions
1097                    .iter()
1098                    .map(|pos| env.read_position(*pos))
1099                    .collect()
1100            };
1101
1102            // 5 is the number of rounds we treat per row
1103            (0..5).fold(state, |state, idx_round| {
1104                let state: Vec<E::Variable> =
1105                    state.iter().map(|x| env.compute_x5(x.clone())).collect();
1106
1107                let round = starting_round + idx_round;
1108
1109                let rcs: Vec<E::Variable> = (0..PlonkSpongeConstants::SPONGE_WIDTH)
1110                    .map(|i| env.get_poseidon_round_constant(round, i))
1111                    .collect();
1112
1113                let state: Vec<E::Variable> = rcs
1114                    .iter()
1115                    .enumerate()
1116                    .map(|(i, rc)| {
1117                        let acc: E::Variable =
1118                            state.iter().enumerate().fold(env.zero(), |acc, (j, x)| {
1119                                acc + env.get_poseidon_mds_matrix(i, j) * x.clone()
1120                            });
1121                        // The last iteration is written on the next row.
1122                        if idx_round == 4 {
1123                            env.write_column(round_output_positions[i], acc + rc.clone())
1124                        } else {
1125                            // Otherwise, we simply allocate a new position
1126                            // in the circuit.
1127                            let pos = env.allocate();
1128                            env.write_column(pos, acc + rc.clone())
1129                        }
1130                    })
1131                    .collect();
1132
1133                // If we are at the last round, we save the state in the
1134                // environment.
1135                // FIXME/IMPROVEME: we might want to execute more Poseidon
1136                // full hash in sequentially, and then save one row. For
1137                // now, we will save the state at the end of the last round
1138                // and reload it at the beginning of the next Poseidon full
1139                // hash.
1140                if round == PlonkSpongeConstants::PERM_ROUNDS_FULL - 1 {
1141                    state.iter().enumerate().for_each(|(i, x)| {
1142                        unsafe { env.save_poseidon_state(x.clone(), i) };
1143                    });
1144                };
1145
1146                state
1147            });
1148        }
1149        Instruction::PoseidonSpongeAbsorb => {
1150            let round_input_positions: Vec<E::Position> = (0..PlonkSpongeConstants::SPONGE_WIDTH
1151                - 1)
1152                .map(|_i| env.allocate())
1153                .collect();
1154
1155            let state: Vec<E::Variable> = round_input_positions
1156                .iter()
1157                .enumerate()
1158                .map(|(i, pos)| env.load_poseidon_state(*pos, i + 1))
1159                .collect();
1160
1161            let values_to_absorb: Vec<E::Variable> = (0..PlonkSpongeConstants::SPONGE_WIDTH - 1)
1162                .map(|_i| unsafe {
1163                    let pos = env.allocate();
1164                    env.fetch_value_to_absorb(pos)
1165                })
1166                .collect();
1167
1168            let output: Vec<E::Variable> = state
1169                .iter()
1170                .zip(values_to_absorb.iter())
1171                .map(|(s, v)| {
1172                    let pos = env.allocate();
1173                    env.write_column(pos, s.clone() + v.clone())
1174                })
1175                .collect();
1176
1177            output
1178                .iter()
1179                .enumerate()
1180                .for_each(|(i, o)| unsafe { env.save_poseidon_state(o.clone(), i + 1) })
1181        }
1182        Instruction::NoOp => {}
1183    }
1184
1185    // Compute the hash of the public input
1186    // FIXME: add the verification key. We should have a hash of it.
1187}
1188
1189/// Describe the control-flow for the verifier circuit.
1190pub fn fetch_next_instruction(current_instruction: Instruction) -> Instruction {
1191    match current_instruction {
1192        Instruction::PoseidonFullRound(i) => {
1193            if i < PlonkSpongeConstants::PERM_ROUNDS_FULL - 5 {
1194                Instruction::PoseidonFullRound(i + 5)
1195            } else {
1196                // FIXME: for now, we continue absorbing because the current
1197                // code, while fetching the values to absorb, raises an
1198                // exception when we absorbed everythimg, and the main file
1199                // handles the halt by filling as many rows as expected (see
1200                // [VERIFIER_CIRCUIT_SIZE]).
1201                Instruction::PoseidonSpongeAbsorb
1202            }
1203        }
1204        Instruction::PoseidonSpongeAbsorb => {
1205            // Whenever we absorbed a value, we run the permutation.
1206            Instruction::PoseidonFullRound(0)
1207        }
1208        Instruction::EllipticCurveScaling(i_comm, bit) => {
1209            // TODO: we still need to substract (or not?) the blinder.
1210            // Maybe we can avoid this by aggregating them.
1211            // TODO: we also need to aggregate the cross-terms.
1212            // Therefore i_comm must also take into the account the number
1213            // of cross-terms.
1214            assert!(i_comm < NUMBER_OF_COLUMNS, "Maximum number of columns reached ({NUMBER_OF_COLUMNS}), increase the number of columns");
1215            assert!(bit < MAXIMUM_FIELD_SIZE_IN_BITS, "Maximum number of bits reached ({MAXIMUM_FIELD_SIZE_IN_BITS}), increase the number of bits");
1216            if bit < MAXIMUM_FIELD_SIZE_IN_BITS - 1 {
1217                Instruction::EllipticCurveScaling(i_comm, bit + 1)
1218            } else if i_comm < NUMBER_OF_COLUMNS - 1 {
1219                Instruction::EllipticCurveScaling(i_comm + 1, 0)
1220            } else {
1221                // We have computed all the bits for all the columns
1222                Instruction::NoOp
1223            }
1224        }
1225        Instruction::EllipticCurveAddition(i_comm) => {
1226            if i_comm < NUMBER_OF_COLUMNS - 1 {
1227                Instruction::EllipticCurveAddition(i_comm + 1)
1228            } else {
1229                Instruction::NoOp
1230            }
1231        }
1232        Instruction::NoOp => Instruction::NoOp,
1233    }
1234}