last day (15 days later) » 

8:22 PM
Cannot wait.
 
The algorithm is called Sometimes-Recurse Shuffle
but tbh, these slides are more clear: ec14.compute.dtu.dk/talks/20.pdf
 
wow. never would have thought of anything like that
 
the core of the algorithm is first deriving some pseudo-random numbers from p
some constant * log(N) of em
given one of those numbers (which are generated from 0 to N-1), we pair numbers
we pair the number at index i with the number at (R - i) % N, where R is one of those random numbers we generated from p
 
this is fascinating
 
note that (R - i) % N is invertible by doing the same operation again (R - ((R - i) % N) - i) % N == i
and very important, max(i, (R-i)%N) will always be the same number for a pair
let's call (R - i)%N i2 for now
the crux of the algorithm is that we do multiple rounds, with different Rs
and at each round we consult a pseudorandom oracle max(i, i2), with some constant different for each round, generating a coinflip
as an example of this, consider taking one bit of sha256
>>> import hashlib
>>> hashlib.sha256(b"test0").digest()[0] & 1
1
>>> hashlib.sha256(b"test1").digest()[0] & 1
1
>>> hashlib.sha256(b"test2").digest()[0] & 1
0
>>> hashlib.sha256(b"test3").digest()[0] & 1
1
this way, you generate deterministic coinflips
so we do this for a couple rounds
if the outcome of the coinflip is 1, we swap i = i2
if the outcome is 0, we don't
conceptually, in each round we randomly make pairs in the array, and randomly swap them or not
but we never track the position of other elements!
and the more rounds we do, the better our randomness gets
 
now, this algorithm is called swap-or-not
for reasons I don't fully understand (read the paper), this only mixes around half the cards after log(N) rounds, and is not sufficient
(err, I started using 'cards' suddenly there, but you know what I mean)
but it does uniformly randomly separate the elements in the left half and right half
so what the algorithm does as a final step, if after all the rounds i < N/2, we recurse, and do the whole thing again, but with limit N/2
 
@orlp I don't understand how this lets you not have P in memory
 
this is an ugly implementation I made in Python
prf_under_n returns a deterministic pseudorandom number under n, given its inputs
prf_coinflip returns a deterministic pseudorandom coinflip, given its inputs
you'd replace those with something more appropriate
@Maltysen do you understand how the pairings are made without P in memory?
 
@orlp ohhh, so you just keep a list of the pairings, i see now
 
@Maltysen not even a list of the pairings
a list of the numbers R that generates the pairings
e.g. for N = 8
if R = 5
0 pairs with (5 - 0) % 8 = 5
1 pairs with (5 - 1) % 8 = 4
2 pairs with (5 - 2) % 8 = 3
3 pairs with (5 - 3) % 8 = 2
4 pairs with (5 - 4) % 8 = 1
5 pairs with (5 - 5) % 8 = 0
6 pairs with (5 - 6) % 8 = 7
7 pairs with (5 - 7) % 8 = 6
see how the pairing is symmetric?
and how you can always compute the other pair from just knowing the current number?
 
8:43 PM
yeah
 
all right
so let's say I give you i = 2
and we're doing currently a round with R = 5
you compute i2 = (R - i) % N = 3
now you do a deterministic, but pseudorandom coinflip
if the coinflip turns out to be 0, we don't swap, so i stays the same
if the coinflip turns out to be 1, there was a swap, so i = i2
@Maltysen the crucial part is, we determine the coinflip based on max(i, i2)
and because the i <-> i2 thing is symmetric, the max is the same for either of them
@Maltysen the crux is rather than making the shuffle locations really random, we use a simple sort-of-random method, but then conditionally apply it
 
i see
 
and by repeatedly doing that we get a high quality random permutation
 
this is cool
 
and you can do that individually for every element
 
in log(n) time and space (per element)
but really, read these slides: ec14.compute.dtu.dk/talks/20.pdf
you can skip the first 7 pages if you're not into crypto
@Maltysen so, if you abstract away the prf_under_n and prf_coinflip pseudo-rng building blocks, this is the algorithm:
def f(n, p, N=10**18):
    if N == 1:
        return n

    round_keys = [prf_under_n(N, p, r) for r in range(7 * math.ceil(math.log(N, 2)))]
    for r, K in enumerate(round_keys):
        n2 = (K - n) % N
        if prf_coinflip(max(n, n2), p, r):
            n = n2

    if n < N // 2:
        return f(n, p, N // 2)

    return n
which, to me, is elegant
 
nice
 
@Maltysen btw, is it clear what prf_coinflip does?
 
@orlp yeah
it takes a "seed" and gives you a random bit
 
@Maltysen the crucial part is that the seed is dependent on 1. p, 2. the round key and 3. max(i, i2)
the result of this is that all coinflips are independent
which is... rather important :)
 
9:05 PM
Now I can't say I didn't learn anything interesting today.
 

last day (15 days later) »