Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use a bijective mixer #4

Open
martinus opened this issue Jan 30, 2020 · 6 comments
Open

use a bijective mixer #4

martinus opened this issue Jan 30, 2020 · 6 comments
Labels
enhancement New feature or request

Comments

@martinus
Copy link

Hi, I saw your talk and have a few suggestions concerning the encryption. I think it is enough to use a good bijective mixer like murmurhash's fmix:

uint64_t encrypt(uint64_t h) {
    h ^= h >> 33;
    h *= 0xff51afd7ed558ccd;
    h ^= h >> 33;
    h *= 0xc4ceb9fe1a85ec53;
    h ^= h >> 33;
    return h;
}

Node that the x ^= x>>n operation basically is used to mix the lower bits, and the multiplications are used to mix the upper bits. Enough of these operations and h is well mixed. Both operations are reversible, so they are bijective.

To support arbitrary number of bits, this should work:

uint64_t encrypt(int bits, uint64_t h) {
    uint64_t shift = bits/2;
    uint64_t mask = (1<<bits) - 1;

    h ^= h >> shift;
    h = (h * 0xff51afd7ed558ccd) & mask;
    h ^= h >> shift;
    h = (h * 0xc4ceb9fe1a85ec53) & mask;
    h ^= h >> shift;
    return h;
}

Although I don't know about the randomness quality.

It is also possible to support a seed for arbitrary random iteration sequences:

uint64_t encrypt(int bits, uint64_t seed, uint64_t h) {
    uint64_t shift = bits/2;
    uint64_t mask = (1<<bits) - 1;

    h ^= (seed & mask);
    h ^= h >> shift;
    h = (h * 0xff51afd7ed558ccd) & mask;
    h ^= h >> shift;
    h = (h * 0xc4ceb9fe1a85ec53) & mask;
    h ^= h >> shift;
    return h;
}

Not that only the last few bits of the seed are actually used, so this could certainly be improved.

@pauldreik
Copy link
Owner

Interesting idea, will give it a try! The best thing is that this also should be possible to accelerate with simd (as in the repo, but not shown in the presentation). I believe it should be below 9 cycles per output value.
It is a fun fact that Björn Fahller rewrote my lazy fisher yates using guess what, the robin hood hash! Small world.

@martinus
Copy link
Author

Ha, the world is really small :)

@pauldreik
Copy link
Owner

For this to work as a block cipher, it needs to be reversible. The xor between the upper and lower bits is reversible. For the prime multiplication, I had to convince myself it was invertible. I knew of the related inverse multiplication trick for regular division (multiply+shift), but I did not know how to actually calculate it. So I borrowed a book on number theory from the work library. This was fun! The resulting algorithm of finding the inversion multiplier is in

static constexpr UInteger inv_prime(const UInteger prime)

What remains is to implement the N-bit version and put it in the shootout. Unfortunately I ran out of time now but will be back later.

@pauldreik pauldreik added the enhancement New feature or request label Feb 2, 2020
@martinus
Copy link
Author

martinus commented Feb 2, 2020

Nice progress! Although I don't know why you actually need to be able to reverse it?

Also I think it's more accurate to say that the reverse works with odd number. E.g it works with 9, but doesn't with the prime 2. I think most of the time it is enough to use a random odd number for the multiply mixing step.

@pauldreik
Copy link
Owner

Nice progress! Although I don't know why you actually need to be able to reverse it?

I don't, I just want to prove that it is a block cipher (instead of just a hash). One way of doing that is to find the decryption function!

Also I think it's more accurate to say that the reverse works with odd number. E.g it works with 9, but doesn't with the prime 2. I think most of the time it is enough to use a random odd number for the multiply mixing step.

Yes, any p with gcd(p,2^N)==1 works. Since all odd numbers lack 2 in their factorization and the factors of 2^N are all 2, they share no common factors and the greatest common divisor is 1.

I did not think of this since I was focused on the particular p from murmurhash. It suggests that one can use the multiplication factor as encryption key. I guess the choice of a prime for murmur is not arbritrary, and also that a particular prime has been selected with good mixing properties.

@pauldreik
Copy link
Owner

Notes to self (I wish I knew about these before I started :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants