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

feat: add upper bound for encrypted random integers #44

Merged
merged 3 commits into from
Dec 18, 2023

Conversation

dartdart26
Copy link
Collaborator

Add an upper bound for encrypted random integers by introducing a separate precompile that does a modulo operation before trivially encrypting the result. Generate encrypted random integers in the [0, upperBound) range where upperBound is a power of 2. If the given upperBound is not a power of 2, return an error. Reason for using powers of 2 is potential performance benefit when using an FHE-based random number generation in comparisson to an arbitrary upperBound.

Avoid potential out-of-bound indexing of input in FheLib. The start index would be invalid if the actual input is exactly 4 bytes - in that case, indexing would start from index 4 and that doesn't exist. Fix by taking the min of 4 and len(input) - 1.

Add unit tests for encrypted random number generation with an upperBound.

Add an upper bound for encrypted random integers by introducing a
separate precompile that does a modulo operation before trivially
encrypting the result. Generate encrypted random integers in the
[0, upperBound) range where upperBound is a power of 2. If the given
upperBound is not a power of 2, return an error. Reason for using
powers of 2 is potential performance benefit when using an FHE-based
random number generation in comparisson to an arbitrary upperBound.

Avoid potential out-of-bound indexing of input in `FheLib`. The start
index would be invalid if the actual input is exactly 4 bytes - in that
case, indexing would start from index 4 and that doesn't exist. Fix by
taking the min of 4 and len(input) - 1.

Add unit tests for encrypted random number generation with an
upperBound.
@mortendahl
Copy link

Heads up that taking the modulus is not a secure way to produce uniform numbers in the given interval. We're mocking everything here, so it's insecure anyways, but this approach is not something we can use for the secure version. One alternative is rejection sampling.

To see the issue, if you for example sample X uniformly from [0,4) then you have:

  • Pr(X=0) = 1/4
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4
  • Pr(X=3) = 1/4

Using this to sample from [0;3) by taking the modulus results in:

  • Pr(X=0) = 1/2
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4

since 0 = 3%3. More generally, the numbers in [0; next-power-of-2 - upper-bound) are twice as likely as the rest.

@dartdart26
Copy link
Collaborator Author

Heads up that taking the modulus is not a secure way to produce uniform numbers in the given interval. We're mocking everything here, so it's insecure anyways, but this approach is not something we can use for the secure version. One alternative is rejection sampling.

To see the issue, if you for example sample X uniformly from [0,4) then you have:

  • Pr(X=0) = 1/4
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4
  • Pr(X=3) = 1/4

Using this to sample from [0;3) by taking the modulus results in:

  • Pr(X=0) = 1/2
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4

since 0 = 3%3. More generally, the numbers in [0; next-power-of-2 - upper-bound) are twice as likely as the rest.

Yes, I am aware of it, but thought since it is a mock, we can do a quick solution. I guess I can fix that and generate however many bits we want from the RNG and/or use bitshifts. Maybe makes sense to do it even if the whole thing is insecure, wdyt?

@mortendahl
Copy link

I guess I can fix that and generate however many bits we want from the RNG and/or use bitshifts. Maybe makes sense to do it even if the whole thing is insecure, wdyt?

I'm not sure what you mean by bitshifts, but yes, makes sense to do it now. It might match what we have to do in the secure version as well (eg sample a few times and use cmux to pick the one that's below).

@dartdart26
Copy link
Collaborator Author

Heads up that taking the modulus is not a secure way to produce uniform numbers in the given interval. We're mocking everything here, so it's insecure anyways, but this approach is not something we can use for the secure version. One alternative is rejection sampling.
To see the issue, if you for example sample X uniformly from [0,4) then you have:

  • Pr(X=0) = 1/4
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4
  • Pr(X=3) = 1/4

Using this to sample from [0;3) by taking the modulus results in:

  • Pr(X=0) = 1/2
  • Pr(X=1) = 1/4
  • Pr(X=2) = 1/4

since 0 = 3%3. More generally, the numbers in [0; next-power-of-2 - upper-bound) are twice as likely as the rest.

Yes, I am aware of it, but thought since it is a mock, we can do a quick solution. I guess I can fix that and generate however many bits we want from the RNG and/or use bitshifts. Maybe makes sense to do it even if the whole thing is insecure, wdyt?

Let me explain my thinking of doing it with modulo.

  1. We get 8 bytes (64 bits) of randomness from ChaCha20. Then, we cast that to an uint64, so the range is a power of 2.
  2. We cast (slice) that to 8, 16 or 32 bits if no upperBound is set - essentially taking parts of the randomness, should be fine.
  3. If the upperBound is set, we do a modulo on the 64 bit value from ChaCha20, but since upperBound is a power of 2, AFAICT, it should be fine.

If the upperBound is not a power of 2, then we can think of a different implementation that also might depend on how tfhe-rs produces randomness, not sure about it.

Does that make sense?

@mortendahl
Copy link

We cast (slice) that to 8, 16 or 32 bits if no upperBound is set - essentially taking parts of the randomness, should be fine.

Yes, no problem here security wise.

If the upperBound is set, we do a modulo on the 64 bit value from ChaCha20, but since upperBound is a power of 2, AFAICT, it should be fine.

Yes, I agree. If the modulus is a power of two then no security issue. But a bitshift would be faster of course.

If the upperBound is not a power of 2, then we can think of a different implementation that also might depend on how tfhe-rs produces randomness, not sure about it.

This is what I suggest rejection sampling for, but there might be more efficient ways.

Does that make sense?

Yes 👍

@dartdart26
Copy link
Collaborator Author

Maybe I can change it such that if we are requesting say an 8 bit integer, I only get 1 byte from ChaCha20 and then, if the upperBound is set to a power of 2, I just do right shifts to get however many random bits we need from that 1 byte. And also for other types, 2 bytes for euint16 and so on.

Use bitshifts instead of modulo for the upperBound in the rand family
of functions.

Also, get only the amount of bytes needed from ChaCha20, based on the
data type.
@dartdart26
Copy link
Collaborator Author

I added a second commit with bitshifts. It is a bit more verbose than mod, but I guess the intent is clearer. Though these implementations will go away at some point.

@dartdart26
Copy link
Collaborator Author

dartdart26 commented Dec 16, 2023

Btw, since we don't generate any random values and just simulate during gas estimation, if there is a decryption that depends on the random value (or is decrypting the random value itself), the decrypted value is always only 1 bits (i.e. max value possible for the type). That might mess up gas estimation as maybe user code expects a value in a certain range as requested by the upperBound.

Since we will most likely remove inline decryptions, I think we can leave as is. What do you think?

@mortendahl
Copy link

Since we will most likely remove inline decryptions, I think we can leave as is. What do you think?

Yes let's just leave as is for now.

@dartdart26 dartdart26 merged commit a7cdfd9 into main Dec 18, 2023
1 check passed
@dartdart26 dartdart26 deleted the petar/rand8-less-bits branch December 18, 2023 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants