File: RandomTestBsi1999.java

package info (click to toggle)
openjdk-21 21.0.8%2B9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 823,976 kB
  • sloc: java: 5,613,338; xml: 1,643,607; cpp: 1,296,296; ansic: 420,291; asm: 404,850; objc: 20,994; sh: 15,271; javascript: 11,245; python: 6,895; makefile: 2,362; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (456 lines) | stat: -rw-r--r-- 19,976 bytes parent folder | download | duplicates (11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
/*
 * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
import java.util.ArrayList;
import java.util.List;
import java.util.PrimitiveIterator;

import java.util.random.*;

import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 * @test
 * @summary test bit sequences produced by classes that implement interface RandomGenerator
 * @bug 8248862
 * @run main/othervm/timeout=400 RandomTestBsi1999
 * @key randomness
 */

public class RandomTestBsi1999 {

    /* A set of tests for pseudorandom number generators inspired by this report:
     *
     *   Werner Schindler.  Functionality Classes and Evaluation Methodology for
     *   Deterministic Random Number Generators, Version 2.0.
     *   Bundesamt fur Sicherheit in der Informationstechnik (BSI).  December 2, 1999.
     *   https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Zertifizierung/Interpretationen/AIS_20_Functionality_Classes_Evaluation_Methodology_DRNG_e.pdf
     *
     * Section F of this report (pp. 19-20) recommends the use of five tests to evaluate a
     * sequence of bits:
     *
     *        Monobit test
     *        Poker test
     *        Run test
     *        Long run test
     *        Autocorrelation test
     *
     * The first four of these tests are in turn taken from this report:
     *
     *   National Institute of Standards and Technology (NIST),
     *   U.S. Department of Commerce.  Security Requirements for
     *   Cryptographic Modules.  Federal Information Processing
     *   Standard (FIPS) 140-1, January 11, 1994.
     *   https://csrc.nist.gov/csrc/media/publications/fips/140/1/archive/1994-01-11/documents/fips1401.pdf
     *
     * The BSI report appears to contain a few typos in transcribing the FIPS 140-1
     * requirements (pp. 44-45); specifically, the last three intervals for the runs test
     * (for lengths 4, 5, and 6+) are given as "223 - 402, 90 - 223, 90 - 223" in the FIPS
     * standard but as "233-402, 90-223, 90-233" in the BSI publication.  A quick
     * mathematical check indicates that the FIPS 140-1 values are correct; therefore we
     * use those values here. In addition, the BSI report specifies a test interval of
     * 2326-2674 for the autocorrelation test, which provides an appropriately small
     * rejection rate if the test were done for only a single value of tau; but because we
     * wish to perform 5000 distinct tests, one for each value of tau in the range 1-5000,
     * that test interval produces too many false positives.  Some calculation shows that
     * the interval 2267-2733 used by the FIPS 140-1 run test for runs of length 1 is
     * appropriate, so we use that interval here for each of the 5000 individual
     * autocorrelation tests.
     *
     * Each of the four FIPS 140-1 tests examines a sequence of 20000 bits.  The
     * autocorrelation test examines a sequence of 10000 bits.  It is convenient to
     * generate a sequence of 20000 bits (which we represent as an array of 20000 bytes)
     * and then apply all five tests, knowing that the autocorrelation test will examine
     * only the first half of the byte array.
     *
     * The descriptions of the tests are quoted from the FIPS 140-1 and BSI reports
     * (with some adjustments of punctuation and mathematical symbols, as well as
     * for our specific choices of test intervals).
     */

    static String currentRNG = "";
    static int failCount = 0;

    static void exceptionOnFail() {
        if (failCount != 0) {
            throw new RuntimeException(failCount + " fails detected");
        }
    }

    static void setRNG(String rng) {
        currentRNG = rng;
    }

    static void fail(String format, Object... args) {
        if (currentRNG.length() != 0) {
            System.err.println(currentRNG);
            currentRNG = "";
        }

        System.err.format("  " + format, args);
        failCount++;
    }

    private static final int SEQUENCE_SIZE = 20000;

    /* The Monobit Test
     *
     *   1. Count the number of ones in the 20,000 bit stream.  Denote this quantity by X.
     *
     *   2. The test is passed if 9,654 < X < 10,346.
     */
    static int monobitTest(String id, byte[] s) {
       // System.out.println("monobit test");
       int count = 0;
       for (int j = 0; j < s.length; j++) {
           count += s[j];
       }
       int monobitFailure = ((9654 < count) && (count < 10346)) ? 0 : 1;
       if (monobitFailure != 0) fail("monobit test failure for %s: count=%d (should be in [9654,10346])\n", id, count);
       return monobitFailure;
    }

    /* The Poker Test
     *
     *   1.  Divide the 20,000 bit stream into 5,000 contiguous 4-bit segments.  Count and
     *   store the number of occurrences of each of the 16 possible 4-bit values.  Denote
     *   f(i) as the number of each 4-bit value i where 0 <= i <= 15.
     *
     *   2.  Evaluate the following: X = (16/5000)(sum[i=0,15] (f(i))**2) - 5000
     *
     *   3.  The test is passed if 1.03 < X < 57.4.
     */

    static int pokerTest(String id, byte[] s) {
       // System.out.println("poker test");
       // Divide the bit sequence into 4-bit chunks, and count the number of times each 4-bit value appears.
       int[] stats = new int[16];
       int v = 0;
       for (int j = 0; j < s.length; j++) {
           v = (v << 1) | s[j];
           if ((j & 3) == 3) {
           ++stats[v];
           v = 0;
           }
       }
       int z = 0;
       for (int k = 0; k < stats.length; k++) {
           z += stats[k]*stats[k];
       }
       double x = (16.0 / (s.length / 4)) * z - (s.length / 4);
       int pokerFailure = ((1.03 < x) && (x < 57.4)) ? 0 : 1;
       if (pokerFailure != 0) fail("poker test failure for %s: x=%g (should be in [1.03,57.4])\n", id, x);
       return pokerFailure;
    }

    /* The Runs Test
     *
     *   1.  A run is defined as a maximal sequence of consecutive bits of either all ones
     *   or all zeros, which is part of the 20,000 bit sample stream.  The incidences of
     *   runs (for both consecutive zeros and consecutive ones) of all lengths (>= 1) in
     *   the sample stream should be counted and stored.
     *
     *   2.  The test is passed if the number of runs that occur (of lengths 1 through 6)
     *   is each within the corresponding interval specified below.  This must hold for
     *   both the zeros and ones; that is, all 12 counts must lie in the specified
     *   interval.  For the purpose of this test, runs of greater than 6 are considered to
     *   be of length 6.
     *
     *        Length of run       Required Interval
     *             1                2,267 - 2,733
     *             2                1,079 - 1,421
     *             3                  502 - 748
     *             4                  223 - 402
     *             5                   90 - 223
     *             6+                  90 - 223
     *
     * The Long Run Test
     *
     *   1 . A long run is defined to be a run of length 34 or more (of either zeros or ones).
     *
     *   2.  On the sample of 20,000 bits, the test is passed if there are NO long runs.
     */
    static int runTestAndLongRunTest(String id, byte[] s) {
       // System.out.println("run test");
       int[][] stats = new int[2][8];
       int count = 0;
       for (int j = 0; j < s.length; j++) {
           ++count;
           if ((j == (s.length - 1)) || (s[j+1] != s[j])) {
           ++stats[s[j]][(count < 6) ? count : (count < 34) ? 6 : 7];
           count = 0;
           }
       }
       stats[0][6] += stats[0][7];
       stats[1][6] += stats[1][7];
       int runFailure = checkRunStats(stats[0]) | checkRunStats(stats[1]);
       if (runFailure != 0) fail("run test failure for %s\n", id);
       int longRunFailure = ((stats[0][7] == 0) && (stats[1][7] == 0)) ? 0 : 1;
       if (longRunFailure != 0) fail("long run test failure for %s\n", id);
       return (runFailure + longRunFailure);
    }

    static int checkRunStats(int[] stats) {
       int runFailure = 0;
       runFailure |= ((2267 <= stats[1]) && (stats[1] <= 2733)) ? 0 : 1;
       runFailure |= ((1079 <= stats[2]) && (stats[2] <= 1421)) ? 0 : 1;
       runFailure |= (( 502 <= stats[3]) && (stats[3] <=  748)) ? 0 : 1;
       runFailure |= (( 223 <= stats[4]) && (stats[4] <=  402)) ? 0 : 1;
       runFailure |= ((  90 <= stats[5]) && (stats[5] <=  223)) ? 0 : 1;
       runFailure |= ((  90 <= stats[6]) && (stats[6] <=  223)) ? 0 : 1;
       return runFailure;
        }

        /* Autocorrelation Test
         *
         *   For tau in {1, ..., 5000}, Z[tau] := sum[j=1,5000] (b[j] ^ b[j+tau]).
         *
         *   The sequence passes the autocorrelation test if every Z[tau] lies within the
         *   interval 2267-2733.
         */
        static int autocorrelationTest(String id, byte[] s) {
       // System.out.println("autocorrelation test");
       int autocorrelationFailure = 0;
       int N = s.length / 4;
       for (int tau = 1; tau <= N; tau++) {
           int count = 0;
           for (int j = 0; j < N; j++) {
           count += (s[j] ^ s[j+tau]);
           }
           // We intentionally use bounds [2267, 2733], which are wider than
           // the bounds [2326, 2674] specified by BSI for this test.
           // The latter bounds produce way too many false positives.
           int singleAutocorrelationFailure = ((2267 < count) && (count < 2733)) ? 0 : 1;
           if (singleAutocorrelationFailure != 0) {
           if (autocorrelationFailure < 8) {
               fail("autocorrelation failure for %s: count=%d (should be in [2267,2733]), tau=%d\n", id, count, tau);
               if (count < 100 || count > 4900) {
                    System.out.print("    ");
               for (int q = 0; q < 50; q++) System.out.print(s[q]);
                    System.out.println();
               }
           }
           }
           autocorrelationFailure += singleAutocorrelationFailure;
       }
       return (autocorrelationFailure == 0) ? 0 : 1;
    }

    static int entireBsi1999Test(String id, byte[] s) {
       return (monobitTest(id, s) +
           pokerTest(id, s) +
           runTestAndLongRunTest(id, s) +
           autocorrelationTest(id, s)
           );
    }

    /* To test a sequence of boolean values from a BooleanSupplier,
     * sequentially extract 20000 boolean values, convert to an array
     * of bytes, and feed them to method {@code entireBsi1999Test}.
     */

    static int testRngBsi1999BooleanOnce(String id, BooleanSupplier theSupplier) {
       int failureCount = 0;
       byte[] s = new byte[SEQUENCE_SIZE];
       // Take the next SEQUENCE_SIZE booleans and test them
       for (int j = 0; j < s.length; j++) {
           s[j] = (theSupplier.getAsBoolean() ? (byte)1 : (byte)0);
       }
       failureCount += entireBsi1999Test(id  + " consecutive", s);
       return failureCount;
    }

    /* To test a sequence of long values from a LongSupplier,
     * two kinds of tests are performed.
     *
     * The first kind of test extracts 313=ceiling(20000/64) long
     * values and concatenates all their bits; the first 20000 bits
     * are converted to a byte array of bits to be tested.  This test is
     * repeated 64 times.
     *
     * The second kind of test focuses on one bit position m (0 <= m < 64);
     * it extracts 20000 long values and uses just bit m from each value
     * to produce an array of bytes to be tested.  This test is performed
     * once for each possible value of m (64 times in all).
     */
    static int testRngBsi1999LongOnce(String id, LongSupplier theSupplier) {
       int failureCount = 0;
       byte[] s = new byte[SEQUENCE_SIZE];
       // Part 1: 64 times, take the next SEQUENCE_SIZE bits and test them
       for (int m = 0; m < 64; m++) {
           long bits = 0;
           int bitCount = 0;
           for (int j = 0; j < s.length; j++) {
           if ((j & 0x3f) == 0) {
               bits = theSupplier.getAsLong();
               // System.out.printf("0x%016x\n", bits);
               bitCount += Long.bitCount((j == (20000 - 32)) ? ((bits << 32) >>> 32) : bits);
           }
           s[j] = (byte)(bits & 1);
           bits >>>= 1;
           }
           // System.out.println(m + ": " + bitCount + " 1-bits");
           failureCount += entireBsi1999Test(id + " consecutive (" + bitCount + " 1-bits)", s);
       }
       // Part 2: for 0 <= m < 64, use bit m from each of the next SEQUENCE_SIZE longs
       for (int m = 0; m < 64; m++) {
           for (int j = 0; j < s.length; j++) {
           s[j] = (byte)((theSupplier.getAsLong() >>> m) & 1);
           }
           failureCount += entireBsi1999Test(id + " bit " + m, s);
       }
       return failureCount;
    }

    /* To test a sequence of ing values from an IntSupplier,
     * two kinds of tests are performed.
     *
     * The first kind of test extracts 625=20000/32 int values and
     * concatenates all their bits; these 20000 bits are converted to
     * a byte array of bits to be tested.  This test is repeated 64
     * times.
     *
     * The second kind of test focuses on one bit position m (0 <= m < 32);
     * it extracts 20000 int values and uses just bit m from each value
     * to produce an array of bytes to be tested.  This test is performed
     * once for each possible value of m (32 times in all).
     */
    static int testRngBsi1999IntOnce(String id, IntSupplier theSupplier) {
       int failureCount = 0;
       byte[] s = new byte[SEQUENCE_SIZE];
       // Part 1: 64 times, take the next SEQUENCE_SIZE bits and test them
       for (int m = 0; m < 64; m++) {
           int bits = 0;
           int bitCount = 0;
           for (int j = 0; j < s.length; j++) {
           if ((j & 0x1f) == 0) {
               bits = theSupplier.getAsInt();
               bitCount += Integer.bitCount(bits);
           }
           s[j] = (byte)(bits & 1);
           bits >>>= 1;
           }
           // System.out.println(m + ": " + bitCount + " 1-bits");
           failureCount += entireBsi1999Test(id + " consecutive (" + bitCount + " 1-bits)", s);
       }
       // Part 2: for 0 <= m < 32, use bit m from each of the next SEQUENCE_SIZE ints
       for (int m = 0; m < 32; m++) {
           for (int j = 0; j < s.length; j++) {
           s[j] = (byte)((theSupplier.getAsInt() >>> m) & 1);
           }
           failureCount += entireBsi1999Test(id + " bit " + m, s);
       }
       return failureCount;
    }

    /* A call to {@code entireBsi1999Test} may report failure even if the source of random
     * bits is quite good, because the test is statistical in nature.  To make the testing
     * procedure more robust, if the first call to {@code entireBsi1999Test} fails, then
     * the failure is ignored if two more calls to {@code entireBsi1999Test} both succeed.
    */

    static boolean testRngBsi1999Boolean(String id, BooleanSupplier theSupplier, int failureTolerance) {
       if (testRngBsi1999BooleanOnce(id, theSupplier) <= failureTolerance) return true;
       fail("testRngBsi1999Boolean glitch");
       return ((testRngBsi1999BooleanOnce(id, theSupplier) <= failureTolerance) &&
           (testRngBsi1999BooleanOnce(id, theSupplier) <= failureTolerance));
    }

    static boolean testRngBsi1999Long(String id, LongSupplier theSupplier, int failureTolerance) {
       if (testRngBsi1999LongOnce(id, theSupplier) <= failureTolerance) return true;
       fail("testRngBsi1999Long glitch");
       return ((testRngBsi1999LongOnce(id, theSupplier) <= failureTolerance) &&
           (testRngBsi1999LongOnce(id, theSupplier) <= failureTolerance));
    }

    static boolean testRngBsi1999Int(String id, IntSupplier theSupplier, int failureTolerance) {
       if (testRngBsi1999IntOnce(id, theSupplier) <= failureTolerance) return true;
       fail("testRngBsi1999Int glitch");
       return ((testRngBsi1999IntOnce(id, theSupplier) <= failureTolerance) &&
           (testRngBsi1999IntOnce(id, theSupplier) <= failureTolerance));
    }

    static void tryIt(RandomGenerator rng, String id, BooleanSupplier theSupplier) {
       System.out.printf("Testing %s %s\n", rng.getClass().getName(), id);
       boolean success = theSupplier.getAsBoolean();
       if (!success) {
           fail("*** Failure of %s %s\n", rng.getClass().getName(), id);
       }
    }

    static void testOneRng(RandomGenerator rng, int failureTolerance) {
       String name = rng.getClass().getName();
       tryIt(rng, "nextInt", () -> testRngBsi1999Int(name + " nextInt", rng::nextInt, failureTolerance));
       tryIt(rng, "nextLong", () -> testRngBsi1999Long(name + " nextLong", rng::nextLong, failureTolerance));
       tryIt(rng, "nextBoolean", () -> testRngBsi1999Boolean(name + " nextBoolean", rng::nextBoolean, failureTolerance));
       tryIt(rng, "ints", () -> testRngBsi1999Int(name + " ints", rng.ints().iterator()::next, failureTolerance));
       tryIt(rng, "longs", () -> testRngBsi1999Long(name + " longs", rng.longs().iterator()::next, failureTolerance));
       {
           PrimitiveIterator.OfDouble iter = rng.doubles().iterator();
           tryIt(rng, "doubles",
             () -> testRngBsi1999Int(name + " doubles",
                         () -> (int)(long)Math.floor(iter.next() * 0x1.0p32),
                         failureTolerance));
       }
       tryIt(rng, "nextDouble",
             () -> testRngBsi1999Int(name + " nextDouble",
                         () -> (int)(long)Math.floor(rng.nextDouble() * 0x1.0p32),
                         failureTolerance));
       tryIt(rng, "nextFloat",
             () -> testRngBsi1999Int(name + " nextFloat",
                         () -> (((int)(long)Math.floor(rng.nextFloat() * 0x1.0p16f) << 16)
                            | (int)(long)Math.floor(rng.nextFloat() * 0x1.0p16f)),
                         failureTolerance));
    }

    public static void main(String[] args) {
        RandomGeneratorFactory.all().forEach(factory -> {
            setRNG(factory.name());

            if (currentRNG.equals("SecureRandom")) {
                // skip because stochastic
            } else if (currentRNG.equals("Random")) {
                // testOneRng(factory.create(59), 1);
                // autocorrelation failure for java.util.Random longs bit 0: count=2207 (should be in [2267,2733]), tau=2819

            } else {
                testOneRng(factory.create(59), 0);
            }
        });

        exceptionOnFail();
    }
}