11

Here is a function, which expressed in C is:

uint32_t f(uint32_t x) {
    return (x * 0x156) ^ 0xfca802c7;
}

Then I came across a challenge: How to find all its fixed points?

I know we can test every uint32_t value to solve this problem, but I still want to know if there is another way that is more elegant - especially when uint32_t becomes uint64_t and (0x156, 0xfca802c7) is an arbitrary pair of values.

duplode
  • 31,361
  • 7
  • 69
  • 130
dploop
  • 113
  • 4
  • 1
    You need to solve the equation `x = x * 0x156 ^ 0xfca802c7` with overflow arithmetic in mind. – aioobe May 04 '16 at 06:03
  • 1
    There are no even fixed points, because if x is even then the return value is odd. That reduces the problem in half. Still a lot to go. – TheGreatContini May 04 '16 at 06:12
  • 1
    I would expect this problem to be difficult. Multiplication and XOR are algebraically "incompatible" operations, and it is not easy to reason about this kind of logic. In fact, there are ciphers and hash functions that rely on mixing addition, multiplication, and XOR (e.g. TEA, MurmurHash) precisely because they are hard to analyze. – Nayuki May 04 '16 at 06:17
  • 1
    I suspect you're going to have to break the problem down to the bit-level. For example, solve `x[0] = (x[0] & a[0]) ^ b[0]`, then solve `x[1] = (x[0] & a[1]) ^ (x[1] & a[0]) ^ b[1]`, etc. The "etc." part will be hard. – MooseBoys May 04 '16 at 06:21
  • 1
    I think you can find an algorithm that works by induction. We already solved it modulo 2. From there lift it to a modulo 4 algorithm: the solution mod 2 is 1, which implies only 1 and 3 are possible solutions mod 4. That's 2 possibilities. Try them, I guess only one works. Next, go mod 8 by lifting the (assumed) one solution mod 4 to the two possibilities mod 8, and so on. This should work. Somebody who is not on the train right now can finish this algorithm off and write it up nicely. Running time I think will be linear. – TheGreatContini May 04 '16 at 06:31
  • oh come on now, don't be a quitter! This a fun problem. Play with it and then the solution will be obvious, I'm willing to bet! – TheGreatContini May 04 '16 at 06:55
  • Exhaustive search takes like a second. 150129329 :) – aioobe May 04 '16 at 07:07
  • @TheGreatContini, what do you mean "we already solved it modulo 2"? And could you elaborate on the inductive step in your solution? – aioobe May 04 '16 at 07:09
  • Sorry guys, will explain tomorrow when the kid is not threatening to smash my computer. In the mean time, see my Python code and try to connect it with my previous comments. – TheGreatContini May 04 '16 at 07:19
  • 1
    This is precisely the sort of thing that I made [this site](http://haroldbot.nl/?q=x*0x156%5E0xfca802c7+%3D%3D+x) for, I could explain how that works too if you want – harold May 04 '16 at 12:03
  • As promised, I returned to my solution to explain it (though it seems the original poster understood it despite my lack of explanation in the post). – TheGreatContini May 04 '16 at 22:05

3 Answers3

15

Python code:

def f(x, n):
    return ((x*0x156)^0xfca802c7) % n


solns = [1]  # The one solution modulo 2, see text for explanation
n = 1
while n < 2**32:
    prev_n = n
    n = n * 2
    lifted_solns = []
    for soln in solns:
        if f(soln, n) == soln:
            lifted_solns.append(soln)
        if f(soln + prev_n, n) == soln + prev_n:
            lifted_solns.append(soln + prev_n)
    solns = lifted_solns

for soln in solns:
    print soln, "evaluates to ", f(soln, 2**32)

Output: 150129329 evaluates to 150129329

Idea behind the algorithm: We are trying to find x XOR 0xfca802c7 = x*0x156 modulo n, where in our case n=2^32. I wrote it this way because the right side is a simple modular multiplication that behaves nicely with the left side.

The main property we are going to use is that a solution to x XOR 0xfca802c7 = x*0x156 modulo 2^(i+1) reduces to a solution to x XOR 0xfca802c7 = x*0x156 modulo 2^i. Another way of saying that is that a solution to x XOR 0xfca802c7 = x*0x156 modulo 2^i translates to one or two solutions modulo 2^(i+1): those possibilities are either x and/or x+2^i (if we want to be more precise, we are only looking at integers between 0, ..., modulus size - 1 when we say "solution").

We can easily solve this for i=1: x XOR 0xfca802c7 = x*0x156 modulo 2^1 is the same as x XOR 1 = x*0 mod 2, which means x=1 is the only solution. From there we know that only 1 and 3 are the possible solutions modulo 2^2 = 4. So we only have two to try. It turns out that only one works. That's our current solution modulo 4. We can then lift that solution to the possibilities modulo 8. And so on. Eventually we get all such solutions.

Remark1: This code finds all solutions. In this case, there is only one, but for more general parameters there may be more than one.

Remark2: the running time is O(max[number of solutions, modulus size in bits]), assuming I have not made an error. So it is fast unless there are many, many fixed points. In this case, there seems to only be one.

TheGreatContini
  • 5,791
  • 1
  • 18
  • 25
5

Let's use Z3 solver:

(declare-const x (_ BitVec 32))
(assert (= x (bvxor (bvmul x #x00000156) #xfca802c7)))
(check-sat)
(get-model)

The result is '#x08f2cab1' = 150129329.

RedX
  • 13,656
  • 1
  • 46
  • 69
Shinjikun
  • 550
  • 1
  • 5
  • 13
  • 2
    Could you please explain a little about how that magic code works? – Sayakiss May 04 '16 at 07:43
  • 1
    @Sayakiss That code only contains the problem definition. The magic happens in `check-sat`, which invokes SAT solver. A SAT solver is a complex program, you can't describe all the strategies one uses in a simple answer. Most likely the poster doesn't understand the details either, just like most of us don't know the details of how the compiler for our language of choice is implemented. – CodesInChaos May 04 '16 at 10:49
  • This blog post describes a primitive SAT solver: [Understanding SAT by Implementing a Simple SAT Solver in Python](http://sahandsaba.com/understanding-sat-by-implementing-a-simple-sat-solver-in-python.html). The Z3 solver used in this answer likely supports a lot more advanced strategies in order to be able to solve more problems efficiently. – CodesInChaos May 04 '16 at 10:50
  • While the problem is a SAT problem, Z3 will almost definitely work in a way that is much faster than twiddling lists of booleans. In fact this is the beauty of SAT/SMT solvers and logical programming languages: you describe the problem and use an incredibly well-written and well-optimized general resolver to find the solution to your problem. – Shinjikun May 12 '16 at 06:57
  • Strictly speaking, Z3 is not a SAT solver. It can be used in more situations and the official category is a "SMT" solver. But in this particular case, yes you can do it in a SAT solver. A bit vector (for example, an integer) is a list of booleans. Both xor and mul can be defined by logical operations on these booleans. Then the equation turn this into SAT problem, which is NPC in terms of number of booleans (bits) involved. – Shinjikun May 12 '16 at 06:59
0

Since input bits at position n only affect output bits at positions ≥ n you know that you can find a solution by choosing the first bit, then the second bit, etc.

Here is how you could solve it in C++ for 64-bit integers (of course it also works with 32-bit integers):

#include <cstdint>
#include <cstdio>

uint64_t f(uint64_t x) {
    return (x * 0x7ef93a76ULL) ^ 0x3550e08f8a9c89c7ULL;
}

static void search(uint64_t x, uint64_t bit)
{
    if (bit == 0)
    {
        printf("Fixed point: 0x%llx\n", (long long unsigned)x);
        return;
    }

    if (f(x + bit) & bit) search(x + bit, bit << 1);
    if ((f(x) & bit) == 0) search(x, bit << 1);
}

int main()
{
    search(0x0, 1);
}

With this output:

Fixed point: 0xb9642f1d99863811
sam hocevar
  • 11,037
  • 5
  • 42
  • 59