4

The game of Bridge is played with 52 different playing cards that are randomly distributed among four players, each player ending up with thirteen cards: a so called "deal". Roughly a little less than 2^96 Bridge deals are possible. In this document the requirements for a program that generates random deals are described as follows:

  1. The software should be able to generate every possible bridge deal, since that is also possible with manual dealing.
  2. The software should generate every deal with the same probability, without being influenced by the board number, previous hands or any other circumstance.
  3. It should be impossible to predict deals, even after having seen all other deals in the session.

This document goes on to state that pseudo random generators cannot be used to generate deals as seeing the first elements of a pseudo random sequence will make it possible to compute the seed used and thus enable a hacker to predict the deals that will follow.

Moreover, as most pseudo random generators take a seed of 32 bits it should follow that these generators will be capable of producing at best 2^32 different Bridge deals as opposed to the required 2^96 and that, following the so called Birthday paradox, it is likely that the same deal will be produced after the square root of 2^32 deals.

The author of the document that describes the requirements of a Bridge deal producing application has written a program, used world wide to produce random deals, that generates a 96 bit seed using a human typing randomly on the keyboard. In fourteen years no flaws have been demonstrated in this approach.

I would like to write a routine that forgoes the need to use human input to generate the seed required.

In comes the RNGCryptoServiceProvider. I used this using the code below to generate random numbers, first in the range 1 to 52, then in the range 1 to 51, and so forth until one card remains.

Testing the resulting deals I am pretty confident that this code is capable of producing any deal it is capable of with the same probability and that the chance of any card ending up with one of the four players equals 0.25.

But as the strength of the seed used in the RNGCryptoServiceProvider is not known to me I am wondering if:

  1. This code will be able to, or can be adapted to, producing 2^96 different deals.
  2. This code's next deal is not going to be predictable.

EDIT The method to obtain random numbers previously mentioned in this question was flawed. This distracted from the main question, being if this code is able to produce 2^96 distinct Bridge deals. I replaced the random number generator with the one published by Stephen Taub and Shawn Farkas in the MSDN magazine

Code used to produce a cryptographically secure random number in the ranges 1-52, 1-51, and so on up to 1-2, taken from this website

     /// <summary>
/// Returns a random number within a specified range.
/// </summary>
/// <returns>
/// A 32-bit signed integer greater than or equal to <paramref name="minValue"/> and less than <paramref name="maxValue"/>; that is, the range of return values includes <paramref name="minValue"/> but not <paramref name="maxValue"/>. If <paramref name="minValue"/> equals <paramref name="maxValue"/>, <paramref name="minValue"/> is returned.
/// </returns>
/// <param name="minValue">The inclusive lower bound of the random number returned.</param>
/// <param name="maxValue">The exclusive upper bound of the random number returned. <paramref name="maxValue"/> must be greater than or equal to <paramref name="minValue"/>.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="minValue"/> is greater than <paramref name="maxValue"/>.</exception>
public override Int32 Next(Int32 minValue, Int32 maxValue)
{
    if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue");
    if (minValue == maxValue) return minValue;
    Int64 diff = maxValue - minValue;

    while (true)
    {
        //The cryptoProvider is of type RNGCryptoServiceProvider.
        cryptoProvider.GetBytes(uint32Buffer); //The uint32Buffer has a size of 4 bytes.
        UInt32 rand = BitConverter.ToUInt32(uint32Buffer, 0);

        Int64 max = (1 + (Int64)UInt32.MaxValue);
        Int64 remainder = max % diff;
        if (rand < max - remainder)
        {
            return (Int32)(minValue + (rand % diff));
        }
    }
}
Peter O.
  • 32,158
  • 14
  • 82
  • 96
Dabblernl
  • 15,831
  • 18
  • 96
  • 148
  • 2
    Your `Next(int maxValue)` method is flawed to start with. It doesn't give an even distribution. Consider `Next(int.MaxValue / 2 + 1)` - every output except `int.MaxValue / 2` has twice the chance of being returned than `int.MaxValue / 2` does, because of the range of `Next()`. – Jon Skeet Sep 02 '14 at 06:04
  • See [this question](http://stackoverflow.com/questions/19201461/random-number-generator-security-bcryptgenrandom-vs-rngcryptoserviceprovider) for insight on the cryptographic security of RNGCryptoServiceProvider. Since this class ultimately calls a Windows API, the security depends on the underlying Windows implementation. – Peter O. Sep 02 '14 at 06:45
  • @Dabblernl: What solution? I didn't suggest anything other than that the code you've provided doesn't give you a uniform distribution. – Jon Skeet Sep 03 '14 at 11:43
  • @JonSkeet I think I found a correct implementation for using the RNGCryptoServiceProvider to get equally distributed values in the 1-52, 1-51,... 1-2 range. – Dabblernl Sep 03 '14 at 22:11

1 Answers1

5

Once you have a random number generator (RNG) that is "truly uniform", do the following:

  1. Initialize the RNG with a seed based on an external source. This could be keyboard input, the program start time, or a number of other things. From my research, it appears that the RNGCryptoServiceProvider already does this part.

  2. Most importantly, at (frequent) intervals, draw a number from the RNG. Just throw the number away, the important part is that you have "cycled" the RNG. If desired, you can make the intervals random to increase unpredictability. More cycles are better, so I would pick a max interval as low as you feel you can go.

    • There are two choices for the random intervals, (a) use a weaker RNG (which they do use), and (b) ignore it. In a slot machine, the user's input (pressing "SPIN") causes the actual RNG draw. Combined with a sufficiently quick cycling interval, it's considered unpredictable enough.

  3. (Somewhat optional) Don't draw all the numbers in a row. Wait a random amount of time (allowing the RNG to cycle a random amount of times) before drawing the next number (or set of numbers). Because the initial draw is likely based on user input, you should be able to get away with drawing them all at once. It can't hurt though.

This is the process used in the gaming (gambling) industry, where unpredictable RNGs are heavily regulated (and of course, required). Draws involving 500 cards (from 100 separate decks) are done using this method. Note that the RNGs used typically take 32-bit seeds, and are perfectly acceptable.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • For steps 2 and 3 where do you get the randomness to power the random selections? A second weaker RNG or is there a better solution that the gambling industry came up with? EDIT: I guess if you used a 32 bit seeded primary RNG + a another 32 bit seeded RNG for the 2nd step + and then a thrid RNG for the 3rd step that would give you a total of 96 bits worth of seed. – Scott Chamberlain Sep 06 '14 at 01:03
  • 1
    @ScottChamberlain There are two choices for the random intervals, (a) use a weaker RNG (which they do use), and (b) ignore it. In a slot machine, the user's input (pressing "SPIN") causes the actual RNG draw. Combined with a sufficiently quick cycling interval, its considered unpredictable enough. – BradleyDotNET Sep 06 '14 at 01:07
  • 1
    Good point, as with most stuff in the crypto/security field. "Impossible to predict/break/ect" is almost never reachable, it is all about making it "good enough" for your threat model. – Scott Chamberlain Sep 06 '14 at 01:09
  • @ScottChamberlain Exactly, the idea is that you would have to have so much knowledge (initial seed, how many cycles have happened, time of actual draw, etc.) that it would be completely impractical to predict the future results, let alone be able to start the game (draw the RNG) at such a time to *force* a particular result. – BradleyDotNET Sep 06 '14 at 01:12
  • So, *is* the implementation of truly uniform , you think? Testing by force seems to indicate that indeed this is so. – Dabblernl Sep 06 '14 at 11:19
  • @Dabblernl It *probably* is. In gaming, we run RNGs against a Chi-Squared test (among others) to verify the distribution: http://en.wikipedia.org/wiki/Chi-squared_test – BradleyDotNET Sep 06 '14 at 14:51
  • And what P value do you accept for the Chi-Squared test to prove that the results are evenly distributed? A value of 0.05 seems very inadequate for this case. – Dabblernl Sep 06 '14 at 20:22
  • @Dabblernl Unfortunately, I'm not familiar with that exact detail. I understnad how the RNGs work, and how they are verified, but I don't know what constitutes an acceptable P value. The only requirement is that the distribution is uniform, the chi-squared test is just a way to prove it to regulators. Sorry I can't be of more help on this detail. – BradleyDotNET Sep 06 '14 at 20:39