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}