tags |
---|
scroll documentation |
code: https://github.com/scroll-tech/misc-precompiled-circuit
link to the original HackMD file: https://hackmd.io/@dieGzUCgSGmRZFQ7SDxXCA/SyHlJiRin
Let
Write
Note that in binary representation
and
Note that the number of iterations
Let Number
) and denote the limb representation of
in little-endian. This guarantees that
In addition,
Let x, y be in U256 and p be the prime used in Modexp, d<p be the remainder, both also in U256. Then the constraints for
Note that k may well overflow U256. For example, let p=2 and x,y are close to
is also a
We use the Chinese Remainder Theorem (CRT):
Chinese Remainder Theorem: Let
In our case, we pick
For any
For an
since
Also,
And by definition
because
So constraints that ensure
To form a circuit for Modexp we shall apply (2) to each iteration step in (1). In the code, method ModexpChip.modexp
fills this purpose, it calls ModexpChip.modmult
to check each step of select
method). The constraints in ModexpChip.modmult
are those listed in (2).
Circuit uses 2-rows for one custom-gate constraint, with layout as follows:
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 |
l1 |
l2 |
l3 |
d |
c0 |
c1 |
c2 |
c3 |
cd |
cdn |
c |
c03 |
c12 |
lookup_hint |
lookup_ind |
sel |
None | None | None | None | dn |
None | None | None | None | None | None | None | None | None | None | None | None |
Note that the above table is a demonstration of how a custom-gate is layouted. The None terms simply stands for the fact that the custom-gate does not touch these cells. It does not mean that the circuit is layouted without these cells being filled with valid values. One can think of the actual circuit layout as the above shaped custom-gate (throwing away the None cells) falling down as in a tetris game.
Here l0
, l1
, l2
, l3
, d
are advice columns (5 advice columns) and rest are fixed columns (12 fixed columns). The context of each cell is explained as follows:
l0
-l3
stand for the 4 limbs in decomposing a U256Number
;d
is a limb term;dn
is a limb term, the next tod
. It is used indecompose_limb
method which decomposes a limb into binary cells, in big endian;c0
-c3
are coefficients for each of the 4 limbs;cd
,cdn
are coefficients ford
anddn
;c03
is the coefficient forl0 * l3
andc12
is the coefficient forl1 * l2
;c
is a constant term added to the constraint equality;sel
is a binary indicator that enables this constraint or not;lookup_hint
is the size of limb lookup forl0
in terms of 12 bits (= one unit in sizesz
), so 108 bits takes size 9, usually use size 10 in case of overflow;lookup_ind
indicates whether to do limb lookup forl0
, so it can be 0u64 (no lookup) and 1u64 (lookup).
Each constraint equality will be written into the following general form ("one-line constraint") with different assignments for the advice and fixed cells:
[c0 * l0 + c1 * l1 + c2 * l2 + c3 * l3 + cd * d + cdn * dn + l0 * l3 * c03 + l1 * l2 * c12 + c] * sel = 0
Modexp Circuit uses assign_line
method to assign these cells. In assign_line
method, witnesses are l0
-l3
, d
, dn
, coefficients are c0
-c3
, cd
, cdn
, c03
, c12
, c
. The sel
cell is taken to be 1, lookup_ind
is 0u64
if lookup_hint ==0
else it is 1u64
. The assign_line
method returns the limbs [l0, l1, l2, l3, d, dn]
.
Range checks of the limbs are done in a separate RangeCheckChip
. The range check is usually applied to l0
limb, and whenever the Modexp circuit needs a range check for the limb it will create a new gate with l0
limb under lookup.
The RangeCheckChip
is connected with the Modexp Circuit via register
method, in which we use lookup argument to ensure that l0
limb is contained as the first acc
term in the RangeCheckChip
and its target lookup size lookup_hint
is contained in the first rem
term in the RangeCheckChip
.
After that, RangeCheckChip
uses provide_lookup_evidence
method to assign values to the range check chip. The range checks are configured by putting constraints that decompose the limb into 12-bit sub-limbs, with each sub-limb under lookup to a table with terms
Below we explain each constraint in (2) with its assignment of these specific cells.
This is done in methods mod_power108m1
, mod_power108m1_mul
and mod_power108m1zero
.
It takes a U256 Number
decomposed into [limb0, limb1, limb2]
(limb3
not known) and computes value=limb0+limb1+limb2
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =limb0 |
l1 =limb1 |
l2 =limb2 |
l3 =None |
d =value |
c0 =1 |
c1 =1 |
c2 =1 |
c3 =None |
cd =-1 |
cdn =None |
c =None |
c03 =None |
c12 =None |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
limb0+limb1+limb2-value==0
.
This method returns [limb0, limb1, limb2, limb0+limb1+limb2]
.
It takes two U256 Number
denoted as lhs
and rhs
, and calls mod_power108m1
to assign each one's limb0-limb2 and obtains
ml=lhs.limb0+lhs.limb1+lhs.limb2
, mr=rhs.limb0+rhs.limb1+rhs.limb2
. Then it computes v=ml*mr
, q
=v / (2^{108}-1)
and r=v-q*(2^{108}-1)
.
After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =q |
l1 =ml |
l2 =mr |
l3 =r |
d =None |
c0 =2^{108}-1 |
c1 =None |
c2 =None |
c3 =1 |
cd =None |
cdn =None |
c =None |
c03 =None |
c12 =-1 |
lookup_hint =10 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
q*(2^{108}-1)-ml*mr+r==0
.
This method returns r
.
It takes 3 limbs limb0, limb1, limb2
and 3 signs sign0, sign1, sign2
, computes v=(2^{108}-1)*16+limb0*sign0+limb1*sign1+limb2*sign2
and q=v/(2^{108}-1)
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =q |
l1 =limb0 |
l2 =limb1 |
l3 =limb2 |
d =None |
c0 =-(2^{108}-1) |
c1 =sign0 |
c2 =sign1 |
c3 =sign2 |
cd =None |
cdn =None |
c =(2^{108}-1)*16 |
c03 =None |
c12 =None |
lookup_hint =1 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
-q*(2^{108}-1)+limb0*sign0+limb1*sign1+limb2*sign2+16*(2^{108}-1)==0
.
This method returns OK
.
Factor 16 here is used as a buffer to make sure that v
is positive, so that we are enabled to do a range check for q
(if v
is negative, the range check for q
becomes hard to perform). In the constraint equation related to mod_power108m1zero
method, each of the limb0
, limb1
, limb2
will not exceed
constraint equation $(x_0+x_1+x_2)(y_0+y_1+y_2) \equiv (k_0+k_1+k_2)(p_0+p_1+p_2) + (d_0+d_1+d_2) \mod (2^{108}-1)$ in (2)
We take two U256 Number
denoted as lhs
(stand for rhs
(stand for
- call
mod_pow108m1_mul
withlhs
$=x$ andrhs
$=y$ to obtainmod_108m1_lhs=
$(x_0+x_1+x_2)\cdot (y_0+y_1+y_2)\mod 2^{108}-1$ ; - call
mod_pow108m1_mul
withlhs
$=k$ andrhs
$=p$ to obtainmod_108m1_rhs=
$(k_0+k_1+k_2)\cdot (p_0+p_1+p_2)\mod 2^{108}-1$ ; - call
mod_power108m1
to obtainmod_108m1_rem=
$d_0+d_1+d_2$ ; - call
mod_power108m1zero
withlimb0=mod_108m1_lhs
,limb1=mod_108m1_rhs
andlimb2=mod_108m1_rem
andsign0=1
,sign1=-1
,sign2=-1
. This checks$(x_0+x_1+x_2)(y_0+y_1+y_2) \equiv (k_0+k_1+k_2)(p_0+p_1+p_2) + (d_0+d_1+d_2) \mod (2^{108}-1)$ in (2).
This is done in methods mod_power216
, mod_power216_mul
and mod_power216_zero
.
It takes a U256 Number
decomposed into [limb0, limb1, limb2]
(limb3
not known) and computes value=limb0+limb1*2^{108}
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =limb0 |
l1 =limb1 |
l2 =None |
l3 =None |
d =value |
c0 =1 |
c1 =2^{108} |
c2 =None |
c3 =None |
cd =-1 |
cdn =None |
c =None |
c03 =None |
c12 =None |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
limb0+limb1*2^{108}-value == 0
.
This method returns value=limb0+limb1*2^{108}
.
It takes two U256 Number
denoted as lhs
and rhs
and then computes v = lhs.limb0 * rhs.limb1 + lhs.limb1 * rhs.limb0
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =lhs.limb0 |
l1 =lhs.limb1 |
l2 =rhs.limb0 |
l3 =rhs.limb1 |
d =v |
c0 =None |
c1 =None |
c2 =None |
c3 =None |
cd =-1 |
cdn =None |
c =None |
c03 =1 |
c12 =1 |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
lhs.limb0*rhs.limb1 + lhs.limb1*rhs.limb0 - v == 0
.
Next, this method computes the quotient q=v/2^{108}
and the remainder r=v-q^2^{108}
, so that this computes r=(lhs.limb0*rhs.limb1+lhs.limb1*rhs.limb0)%2^{108}
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =q |
l1 =v |
l2 =None |
l3 =r |
d =None |
c0 =2^{108} |
c1 =-1 |
c2 =None |
c3 =1 |
cd =None |
cdn =None |
c =None |
c03 =None |
c12 =None |
lookup_hint =10 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
q*2^{108}-v+r == 0
.
After getting the r
, final step is to compute an updated v=lhs.limb0*rhs.limb0+r*2^{108}
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =r |
l1 =lhs.limb0 |
l2 =rhs.limb0 |
l3 =None |
d =v |
c0 =2^{108} |
c1 =None |
c2 =None |
c3 =None |
cd =-1 |
cdn =None |
c =None |
c03 =None |
c12 =1 |
lookup_hint =0 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
r*2^{108}+lhs.limb0*rhs.limb0-v == 0
.
This method finally returns v=lhs.limb0*rhs.limb0+((lhs.limb0*rhs.limb1+lhs.limb1*rhs.limb0) % 2^{108})*2^{108}
.
This method takes 3 limbs limb0
, limb1
, limb2
and 3 signs sign0
, sign1
, sign2
and computes v=8*2^{216}+limb0*sign0+limb1*sign1+limb2*sign2
, q=v/2^{216}
. After these results are obtained it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =q |
l1 =limb0 |
l2 =limb1 |
l3 =limb2 |
d =None |
c0 =-2^{216} |
c1 =sign0 |
c2 =sign1 |
c3 =sign2 |
cd =None |
cdn =None |
c =2*2^{216} |
c03 =None |
c12 =None |
lookup_hint =1 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
-q*2^{216}+limb0*sign0+limb1*sign1+limb2*sign2+8*2^{216} == 0
.
This method returns OK
.
Factor 8 here is used as a buffer (BUFMULT
) to make sure that v
is positive, so that we are enabled to do a range check for q
(if v
is negative, range check for q
becomes hard to perform). In the constraint equation related to mod_power216zero
method, each of the limb0
, limb1
, limb2
will not exceed
constraint equation $x_0y_0 + (x_1y_0+x_0y_1)\cdot 2^{108} \equiv k_0p_0 + (k_1p_0+k_0p_1)\cdot 2^{108} + d_0+d_1\cdot 2^{108} \mod 2^{216}$ in (2)
We take two U256 Number
denoted as lhs
(stand for rhs
(stand for
- call
mod_pow216_mul
withlhs
$=x$ andrhs
$=y$ to obtainmod_216_lhs=
$x_0y_0+(x_1y_0+x_0y_1 \mod 2^{108})\cdot 2^{108}$ ; - call
mod_pow216_mul
withlhs
$=k$ andrhs
$=p$ to obtainmod_216_rhs=
$k_0p_0+(k_1p_0+k_0p_1 \mod 2^{108})\cdot 2^{108}$ ; - call
mod_power216
to obtainmod_216_rem=
$d_0+d_1\cdot 2^{108}$ ; - call
mod_power216zero
withlimb0=mod_216_lhs
,limb1=mod_216_rhs
andlimb2=mod_216_rem
andsign0=1
,sign1=-1
,sign2=-1
. This checks$x_0y_0 + (x_1y_0+x_0y_1)\cdot 2^{108} \equiv k_0p_0 + (k_1p_0+k_0p_1)\cdot 2^{108} + d_0+d_1\cdot 2^{108} \mod 2^{216}$ in (2).
This makes use of mod_native_mul
method.
This method takes 5 U256 Number
named as lhs
, rhs
, rem
, modulus
, quotient
. Then it assigns the following cells
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =modulus.limb3 |
l1 =lhs.limb3 |
l2 =rhs.limb3 |
l3 =quotient.limb3 |
d =rem.limb3 |
c0 =None |
c1 =None |
c2 =None |
c3 =None |
cd =-1 |
cdn =None |
c =None |
c03 =-1 |
c12 =1 |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
So its one-line constraint is given by the equation
-modulus.limb3*quotient.limb3+lhs.limb3*rhs.limb3-rem.limb3==0
.
This method returns rem.limb3
.
We take two U256 Number
denoted as lhs
(stand for rhs
(stand for
- call
mod_native_mul
withlhs
$=x$ ,rhs
$=y$ ,rem
$=d$ ,quotient
$=k$ ,modulus
$=p$ . This checks$x_3y_3\equiv k_3p_3 + d_3 \mod r$ in (2). (Since field is$\mathbb{F}_r$ , operation$\mod r$ is done automatically when doing field operations.)
A remaining constraint in (2) to ensure that lt_number
and le_limb
functions.
This method takes two U256 Number
denoted as lhs
and rhs
and returns 1
if lhs<rhs
, returns 0
if otherwise.
To achieve this, it divides lhs
and rhs
into limbs lhs.limb[0], lhs.limb[1], lhs.limb[2]
and rhs.limb[0], rhs.limb[1], rhs.limb[2]
. Then
- if
lhs.limb[2]<rhs.limb[2]
, returns1
; - elseif
lhs.limb[1]<rhs.limb[1]
, returns1
; - elseif
lhs.limb[0]<rhs.limb[0]
, returns1
; - else returns
0
.
To compare each lhs.limb[i]<rhs.limb[i]
, it calls le_limb
method to compare if lhs.limb[i]>=rhs.limb[i]
.
This method takes two U256 Number
denoted as lhs
and rhs
and returns 1
if lhs<=rhs
and 0
otherwise.
It computes
diff_true = rhs - lhs
;diff_false = lhs - rhs - 1
;abs = rhs - lhs
ifrhs >= lhs
elseabs = lhs - rhs - 1
ifrhs < lhs
;res = 1
ifrhs >= lhs
elseres = 0
ifrhs < lhs
;
Then it fills the following constraints
- Checks
-diff_true+rhs-lhs==0
via the following cell assignment
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =diff_true |
l1 =rhs |
l2 =lhs |
l3 =None |
d =None |
c0 =-1 |
c1 =1 |
c2 =-1 |
c3 =None |
cd =None |
cdn =None |
c =None |
c03 =None |
c12 =None |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
Then set true_limb=diff_true
- Checks
-diff_false-rhs+lhs-1==0
via the following cell assignment
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =diff_false |
l1 =rhs |
l2 =lhs |
l3 =None |
d =None |
c0 =-1 |
c1 =-1 |
c2 =1 |
c3 =None |
cd =None |
cdn =None |
c =-1 |
c03 =None |
c12 =None |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
Then set false_limb=diff_false
- Checks
res(abs-true_limb)==0
via the following cell assignment
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =abs |
l1 =res |
l2 =true_limb |
l3 =res |
d =None |
c0 =None |
c1 =None |
c2 =None |
c3 =None |
cd =None |
cdn =None |
c =None |
c03 =1 |
c12 =-1 |
lookup_hint =0 |
lookup_ind =0u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
- Checks
(res-1)(abs-false_limb)==0
via the following cell assignment
advice | advice | advice | advice | advice | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed | fixed |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
l0 =res |
l1 =res |
l2 =abs |
l3 =false_limb |
d =None |
c0 =None |
c1 =None |
c2 =-1 |
c3 =1 |
cd =None |
cdn =None |
c =None |
c03 =-1 |
c12 =1 |
lookup_hint =9 |
lookup_ind =1u64 |
sel =1 |
None | None | None | None | dn =None |
None | None | None | None | None | None | None | None | None | None | None | None |
This method returns res
.