\myheading{Proving bizarre XOR alternative using SAT solver} \label{weird_XOR_SAT} \renewcommand{\CURPATH}{proofs/XOR_SAT} Now let's try to prove it using SAT. We would build an electric circuit for $x \oplus y = -2*(x \& y) + (x + y)$ like that: \iffalse \begin{lstlisting}[basicstyle=\small] x -+--- +---+ | |AND| --- +---+ y -|-+- +---+ |MUL| --- +---+ | | -2 +---+ |ADD| --- +---+ | | x - +---+ |ADD| --> out1 --- +------+ | | y - +---+ | | | | | EQ | ---> must be true +-|- +---+ | | | |XOR| --------------------------------> out2 --- +------+ +- +---+ \end{lstlisting} \fi \begin{figure}[H] \includesvg[width=\textwidth]{\CURPATH/1.svg} \end{figure} And now we can implement EQ block using XOR and OR: \iffalse \begin{lstlisting}[basicstyle=\small] x -+--- +---+ | |AND| --- +---+ y -|-+- +---+ |MUL| --- +---+ | | -2 +---+ |ADD| --- +---+ | | x - +---+ |ADD| --> out1 --- +-------+ | | y - +---+ | | +----+ | | | XOR | ---> | OR | ---> must be 0 +-|- +---+ | | +----+ | |XOR| --------------------------------> out2 --- +-------+ +- +---+ \end{lstlisting} \fi \begin{figure}[H] \includesvg[width=\textwidth]{\CURPATH/2.svg} \end{figure} So it has two parts: generic XOR block and a block which must be equivalent to XOR. Then we compare its outputs using XOR and OR. If outputs of these parts are always equal to each other for all possible x and y, output of the whole block must be 0. I do otherwise, I'm trying to find such an input pair, for which output will be 1: \begin{lstlisting}[style=custompy] def chk1(): input_bits=8 s=SAT_lib.SAT_lib(False) x,y=s.alloc_BV(input_bits),s.alloc_BV(input_bits) step1=s.BV_AND(x,y) minus_2=[s.const_true]*(input_bits-1)+[s.const_false] product=s.multiplier(step1,minus_2)[input_bits:] result1=s.adder(s.adder(product, x)[0], y)[0] result2=s.BV_XOR(x,y) s.fix(s.OR(s.BV_XOR(result1, result2)), True) if s.solve()==False: print ("unsat") return print ("sat") print ("x=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(x))) print ("y=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(y))) print ("step1=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(step1))) print ("product=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(product))) print ("result1=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(result1))) print ("result2=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(result2))) print ("minus_2=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(minus_2))) \end{lstlisting} The full source code: \url{\RepoURL/\CURPATH/XOR_SAT.py}. SAT solver returns "unsat", meaning, it couldn't find such a pair. In other words, it couldn't find a counterexample. So the circuit always outputs 0, for all possible inputs, meaning, outputs of two parts are always the same. Modify the circuit, and the program will find such a state, and print it. That circuit also called "miter". According to Google translate, one meaning of the word is: \begin{lstlisting} a joint made between two pieces of wood or other material at an angle of 90°, such that the line of junction bisects this angle. \end{lstlisting} It's also slow, because multiplier block is used: so we use small 8-bit x's and y's. But the whole thing can be rewritten: $x \oplus y = x+y - (x \& y)<<1$. And subtraction is addition, but with one negated operand. So, $x \oplus y = (-(x \& y))<<1 + (x + y)$ or $x \oplus y = (x \& y)*2 - (x + y)$. \iffalse \begin{lstlisting}[basicstyle=\footnotesize] x --- +---+ +---+ +---+ |AND| --- |NEG| --- |<<1| -+ y --- +---+ +---+ +---+ +- +---+ |ADD| --- +---+ x - +---+ |ADD| --> out1 --- +-------+ y - +---+ | | +----+ | XOR | ---> | OR | ---> must be 0 x --- +---+ | | +----+ |XOR| ------------------------------------------> out2 --- +-------+ y --- +---+ \end{lstlisting} \fi \begin{figure}[H] \includesvg[width=\textwidth]{\CURPATH/3.svg} \end{figure} \TT{NEG} is negation block, in two's complement system. It just inverts all bits and adds 1: \begin{lstlisting}[style=custompy] def NEG(self, x): # invert all bits tmp=self.BV_NOT(x) # add 1 one=self.alloc_BV(len(tmp)) self.fix_BV(one,n_to_BV(1, len(tmp))) return self.adder(tmp, one)[0] \end{lstlisting} Shift by one bit does nothing except rewiring. That works way faster, and can prove correctness for 64-bit x's and y's, or for even bigger input values: \begin{lstlisting}[style=custompy] def chk2(): input_bits=64 s=SAT_lib.SAT_lib(False) x,y=s.alloc_BV(input_bits),s.alloc_BV(input_bits) step1=s.BV_AND(x,y) step2=s.shift_left_1(s.NEG(step1)) result1=s.adder(s.adder(step2, x)[0], y)[0] result2=s.BV_XOR(x,y) s.fix(s.OR(s.BV_XOR(result1, result2)), True) if s.solve()==False: print ("unsat") return print ("sat") print ("x=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(x))) print ("y=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(y))) print ("step1=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(step1))) print ("step2=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(step2))) print ("result1=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(result1))) print ("result2=%x" % SAT_lib.BV_to_number(s.get_BV_from_solution(result2))) \end{lstlisting} The source code: \url{\RepoURL/\CURPATH/XOR_SAT.py}.