It seems that in order to compare a variable a
and a constant c
, all we need is to compare them bitwise. Since c
is a constant, we do not need to introduce any new witness variables to get its unpacking. We can just directly use the bits of c
as constants without specifying anywhere that those bits come from the c
constant.
We'll use the convention that the most significant bit goes first and instead of R1CS we'll use the constraint system of Sonic/Bulletproofs.
Everything is summarized in the following truth table:
ai ci r res
0 0 r r
1 0 r 1
0 1 r 0
1 1 r r
Here ai
and ci
are particular bits of a
and c
and r
is the rest of the computation. This reads as "if ai
equals ci
(two bits are equal, the 1 and 4 cases), then look at lower bits, otherwise if ai
is 1 while ci
is 0, then return 1
, otherwise return 0
". I.e. we compare the bits of the variable and the constant until there's a mismatch, in which case we return the result depending on whether it's the variable's bit is 1
or the constant's one.
Since ci
is known statically, we can break this truth table down into two cases: ci = 0
and ci = 1
. Which we can compile as
ci = 0: res = ai OR r
ci = 1: res = ai AND r
If we ignore the problem of compiling consecutive OR
s and AND
s efficiently, then this becomes:
ci = 0: res = ai + r - ai * r
ci = 1: res = ai * r
Let us now look at some examples. Consider a variable v
that fits into 3 bits, which we compile as:
_3 = _3 * _3
_2 = _2 * _2
_1 = _1 * _1
0 = - v + _1 + 2 * _2 + 4 * _3
where _3
is the most significant bit, _1
is the least significant bit and _2
is in the middle. The first three equations ensure that _1
, _2
and _3
are indeed bits (i.e. can either be 0
or 1
). The last equation ensures that those bits together represent the v
variable in binary form.
Now let's see what constraints we add to the above set if we compare the v
variable and some constant c
(v > c
) and compile that.
For c = 7 we get
res = 0
I.e. there is no three-bit number that is greater than 7
.
For c = 6
we get
_4 = _2 * _1
res = _3 * _4
The only three-bits number that is greater than 6
is 7
, which is represented as 111
in binary form. I.e. all bits must be equal to 1
. And this is exactly what the equations above say: if all bits of a three-bits number are 1
, then the result is 1
, i.e. the number is bigger than 6
, otherwise the result is 0
.
For c = 5
we get
res = _3 * _2
There are two three-bit numbers that are greater than 5
: 6
(110
) and 7
(111
). I.e. a three-bit number is bigger than 5
whenever its two most significant bits are both 1
and this is exactly what the equation above says.
For c = 4
we get
_4 = _2 * _1
_5 = _1 + _2 - _4
res = _3 * _5
There are three three-bit numbers that are greater than 4
: 5
(101
), 6
(110
) and 7
(111
). I.e. the most significant bit must be 1
(the _3
part of the last equation) and (the *
part of the last equation) the disjunction of the other two bits must be 1
as well (the _5
part pf the last equation). That disjunction is _2 or _1
and we compile this down to the first two constraints as per usual.
For c = 3
we get
res = _3
There are four three-bit numbers that are greater than 3
: 4
(100
), 5
(101
), 6
(110
) and 7
(111
) and all of them start with 1
, i.e. the most significant bit of v
must be equal to 1
in order for v > 3
to hold.
This approach can be easily extended to handle the variable < constant
as well as variable < variable
cases.