Impuzzible
Introduction
For this challenge we are given a game for which we must write two players, Alice and Bob. Alice is given an integer $k$ between $0$ and $255$ and a $256$ element array $xs$ of booleans. Alice then chooses one of these booleans $i$ to flip. Bob is then given the array $xs$ with the $i$th boolean flipped and must determine the integer $k$ originally given to Alice.
The algorithm
The intuition for our approach is to associate with every element of $xs$ its index $j$ and then let Bob return the xor sum $t$ of the indexes $j$ for which $xs$ is $True$.
$$t = \bigoplus_{j \textrm{ for which } xs[j] = True} j$$
This allows alice to ensure that this gives the correct value simply by calculating $t$ for the original $xs$ and then requesting that $i = t \oplus k$ is flipped. The python code for Alice and Bob is then essentially:
|
|
Limits
The challenge here is that we cannot simply give a python function as the solution, but must deliver python bytecode which is inserted into functions of the form:
|
|
This limits the amount of scratch space we have available. Alice has two variables (the parameters) available while Bob only has one. Additionally both functions only have a single spot available on the their stacks, at least according to spec. By experimentation we determined that Alice was able to use 3 stack spots without crashing while Bob could use just 2. Also the bytecode must be for Python 3.9 as this was what the challenge was running.
Bytecode
Generally our bytecode mimics the Python code presented above. In an attempt to save stack and variable space all of our state is packed into a tuple $(t, j, xs)$ which is then stored in the variable slot where $xs$ was passed to the function.
Alice
|
|
This initial part loads in $k$, creates a $0$, and loads $xs$, packages these as the tuple $(t, j, xs)$ and stores it back to variable slot $0$,
|
|
This section unpacks our tuple on to the stack and drops $t$ as it is unneeded. Then we retrieve $xs[j]$ and if $False$ we skip the next section.
|
|
This section pulls out $t$ and $j$ from our tuple $(t, j, xs)$ and stores back $(t \oplus j, j, xs)$.
|
|
This section pulls out $j$ and calculates $j + 1$. It also stores back the reduced tuple $(t, xs)$.
|
|
Now we compare $j + 1$ to $256$ and if these are equal we jump to the final block.
|
|
This section loads the reduced tuple $(t, xs)$ and combines it with $i + 1$ and stores it back as $(t, i + 1, xs)$. It then jumps back to the second block to start over.
|
|
This block essentially cleans the stack and loads the reduced tuple $(t, xs)$ to return $t$.
This implementation uses just one variable slot and three stack slots which is appropriate for Alice.
Bob
For Bob we would like to reuse as much as possible of Alice’s code, however we only have two stack slots as opposed to the three used by Alice. However it turns out that the variable slots are located just below the stack, so if we can move our variable out of the way, we essentially have a stack slot $-1$. This gives us the necessary stack slots.
Luckily (for us) STORE_FAST
does not really do any bounds checking on the slot number given allowing us to put variables in arbitrary locations. We found that values around $0\mathrm{x}8000$ would often not crash the Python interpreter. In particular $0\mathrm{x}7\mathrm{ffe}$ was used successfully on the challenge setup.
|
|
This is essentially the magic. At this point the stack is empty so our top-of-stack is actually our parameter $xs$. This is moved out of the way and letting us use an extra stack slot.
|
|
Initialization of our tuple here is slightly different from Alice as we need to generate zeroes without having access to any numbers. Instead we ask whether $xs$ contains $xs$, which is of course $False$ and it turns out that $False + False = 0$. So we can initialize $t$ and $j$ to $0$ and create our tuple $(t, j, xs)$. The next long block is equivalent to Alice.
|
|
Finally at the very end we have some slightly different clean-up code as we wish to leave $xs$ behind in the variable slot, or stack slot $-1$, just as we found.
|
|
And thus is how we solved this challenge.