-
Notifications
You must be signed in to change notification settings - Fork 242
Description
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]]andout = [1, 0, 0, 0] = 0001 = {-15, 1} - Perform 1 - 15 = -14. Where
"in": [[1, 0, 0, 0], [1, 1, 1, 1]]andout = [0, 1, 0, 0] = 0010 = {-14, 2} - Perform 2 - 15 = -13. Where
"in": [[0, 1, 0, 0], [1, 1, 1, 1]]andout = [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]. Outputsout=15 - 14 in binary = 1110 =
"in": [0, 1, 1, 1]. Outputsout=14 - 8 in binary = 1000 =
"in": [0, 0, 0, 1]. Outputsout=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
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.