Preface
I checked GitHub repos that included both BinSub and Bits2Num in their circom files with GitHub's search: "BinSub" AND "Bits2Num" language:circom
I was surprised to find zero live projects affected. Because it appears no live projects are at risk, I'm publishing this bug report publicly.
Overview
BinSub template gracefully handles negative values obtained from subtraction by outputting it's binary out array in Twos Complement form.
For example, if we instantiate BinSub(4):
- Perform 0 - 15 = -15. Where
"in": [[0, 0, 0, 0], [1, 1, 1, 1]] and out = [1, 0, 0, 0] = 0001 = {-15, 1}
- Perform 1 - 15 = -14. Where
"in": [[1, 0, 0, 0], [1, 1, 1, 1]] and out = [0, 1, 0, 0] = 0010 = {-14, 2}
- Perform 2 - 15 = -13. Where
"in": [[0, 1, 0, 0], [1, 1, 1, 1]] and out = [1, 1, 0, 0] = 0011 = {-13, 3}
- ...and so on
We see the out array represent the correct solution in Twos Complement, but also represents it's sister positive number.
Bits2Num template assumes incoming binary representations are always positive decimal numbers.
For example, if we instantiate Bits2Num(4):
- 15 in binary = 1111 =
"in": [1, 1, 1, 1]. Outputs out=15
- 14 in binary = 1110 =
"in": [0, 1, 1, 1]. Outputs out=14
- 8 in binary = 1000 =
"in": [0, 0, 0, 1]. Outputs out=8
- ...and so on
We can see this is a problem anytime the developer wants a negative binary result from BinSub to be translated into it's negative decimal representation through Bits2Num.
Importantly, Bits2Num is silently outputting these incorrect positive numbers, rather than reverting.
Developer makes the incorrect natural assumption: "BinSub allows for negative outputs and handles stuff like 4-10 just fine. There is no negative-specific template for Bits2Num, so it'll handle all positive and negative results from BinSub fine too."
Notice there is already a Num2BitsNeg template which handles conversion of negative numbers into their binary representation. However, there is no Bits2NumNeg capable of handling Twos Complement negative binary values.
Tangential note: Num2BitsNeg seems to perform p - in, rather than outputting in Twos Complement? Not sure that's ideal/intended?
Proof of Concept
zkREPL
pragma circom 2.0.0;
template BinSub(n) {
signal input in[2][n];
signal output out[n];
signal aux;
var lin = 2**n;
var lout = 0;
var i;
for (i=0; i<n; i++) {
lin = lin + in[0][i]*(2**i);
lin = lin - in[1][i]*(2**i);
}
for (i=0; i<n; i++) {
out[i] <-- (lin >> i) & 1;
// Ensure out is binary
out[i] * (out[i] - 1) === 0;
lout = lout + out[i]*(2**i);
}
aux <-- (lin >> n) & 1;
aux*(aux-1) === 0;
lout = lout + aux*(2**n);
// Ensure the sum;
lin === lout;
}
template Bits2Num(n) {
signal input in[n];
signal output out;
var lc1=0;
var e2 = 1;
for (var i = 0; i<n; i++) {
lc1 += in[i] * e2;
e2 = e2 + e2;
}
lc1 ==> out;
}
template BadOutput(n) {
signal input a[n]; // 6 in binary = 0110 = [0, 1, 1, 0]
signal input b[n]; // 11 in binary = 1011 = [1, 1, 0, 1]
signal output out;
component binsub = BinSub(n); // 6 - 11 = -5
binsub.in[0] <== a;
binsub.in[1] <== b;
component bits2num = Bits2Num(n);
bits2num.in <== binsub.out; // -5 in twos complement binary
out <== bits2num.out; // outputs 11 instead of -5
}
component main = BadOutput(4);
/*
INPUT = {
"a": [0, 1, 1, 0],
"b": [1, 1, 0, 1]
}
*/
Recommended Fix
It probably doesn't make sense to stop BinSub from outputting negative results in Twos Complement.
You could add a bit flag attached to the output of BinSub. If b > a (negative result) then flag is 1, else flag is 0.
Then handle this flag accordingly in Bits2Num, allowing it to handle Twos Complement negative representations and normal positive representations.
The flag's Boolean state represents whether or not a binary array is a negative Twos Complement or not.
Preface
I checked GitHub repos that included both
BinSubandBits2Numin their circom files with GitHub's search: "BinSub" AND "Bits2Num" language:circomI was surprised to find zero live projects affected. Because it appears no live projects are at risk, I'm publishing this bug report publicly.
Overview
BinSub template gracefully handles negative values obtained from subtraction by outputting it's binary
outarray in Twos Complement form.For example, if we instantiate
BinSub(4):"in": [[0, 0, 0, 0], [1, 1, 1, 1]]andout = [1, 0, 0, 0] = 0001 = {-15, 1}"in": [[1, 0, 0, 0], [1, 1, 1, 1]]andout = [0, 1, 0, 0] = 0010 = {-14, 2}"in": [[0, 1, 0, 0], [1, 1, 1, 1]]andout = [1, 1, 0, 0] = 0011 = {-13, 3}We see the
outarray represent the correct solution in Twos Complement, but also represents it's sister positive number.Bits2Num template assumes incoming binary representations are always positive decimal numbers.
For example, if we instantiate
Bits2Num(4):"in": [1, 1, 1, 1]. Outputsout=15"in": [0, 1, 1, 1]. Outputsout=14"in": [0, 0, 0, 1]. Outputsout=8We can see this is a problem anytime the developer wants a negative binary result from
BinSubto be translated into it's negative decimal representation throughBits2Num.Importantly,
Bits2Numis silently outputting these incorrect positive numbers, rather than reverting.Developer makes the incorrect natural assumption: "
BinSuballows for negative outputs and handles stuff like 4-10 just fine. There is no negative-specific template forBits2Num, so it'll handle all positive and negative results fromBinSubfine too."Notice there is already a Num2BitsNeg template which handles conversion of negative numbers into their binary representation. However, there is no
Bits2NumNegcapable of handling Twos Complement negative binary values.Tangential note:
Num2BitsNegseems to performp - in, rather than outputting in Twos Complement? Not sure that's ideal/intended?Proof of Concept
zkREPL
Recommended Fix
It probably doesn't make sense to stop
BinSubfrom outputting negative results in Twos Complement.You could add a bit flag attached to the output of
BinSub. Ifb > a(negative result) then flag is 1, else flag is 0.Then handle this flag accordingly in
Bits2Num, allowing it to handle Twos Complement negative representations and normal positive representations.The flag's Boolean state represents whether or not a binary array is a negative Twos Complement or not.