org.cryptoworkshop.ximix.common.util.challenge.SeededChallenger.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptoworkshop.ximix.common.util.challenge.SeededChallenger.java

Source

/**
 * Copyright 2013 Crypto Workshop Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cryptoworkshop.ximix.common.util.challenge;

import java.util.BitSet;

import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.prng.EntropySource;
import org.bouncycastle.crypto.prng.EntropySourceProvider;
import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
import org.bouncycastle.crypto.prng.drbg.SP80090DRBG;
import org.cryptoworkshop.ximix.common.util.IndexNumberGenerator;

/**
 * A challenger uses a provided seed in conjunction with a hash function
 * to provide challenges for just under or equal to 50% of the bulletin board contents.
 * <p>
 * As the step number increments the challenger mirrors its internal bitset in order to provide
 * fullest coverage of the over 2 sets of challenges.
 * </p>
 */
public class SeededChallenger implements IndexNumberGenerator {
    private final int max;
    private final BitSet bitSet;
    private final boolean isMirror;

    private int counter;
    private int startIndex;

    /**
     * Base constructor.
     *
     * @param size the number of messages on the board we are issuing challenges on.
     * @param stepNo the number of the step in the shuffling process.
     * @param seed a random seed for creating index numbers to challenge on - must be at least 55 bytes.
     */
    public SeededChallenger(Integer size, Integer stepNo, byte[] seed) {
        this.counter = 0;
        this.startIndex = 0;

        this.bitSet = buildBitSet(size, new HashSP800DRBG(new SHA256Digest(), 256,
                new SingleEntropySourceProvider(seed).get(440), null, null));
        this.isMirror = (((seed[seed.length - 1] & 0xff) + stepNo) & 0x01) == 0;
        this.max = (isMirror) ? (size - (size / 2)) : (size / 2);
    }

    @Override
    public synchronized boolean hasNext() {
        return counter != max;
    }

    @Override
    public synchronized int nextIndex() {
        int ret = (isMirror) ? bitSet.nextClearBit(startIndex) : bitSet.nextSetBit(startIndex);

        startIndex = ret + 1;

        counter++;

        return ret;
    }

    private BitSet buildBitSet(int size, SP80090DRBG drbg) {
        BitSet bitSet = new BitSet(size);
        int nBits = size / 2;

        int upper = size - 1;
        int lower = 0;

        for (int i = 0; i != nBits; i++) {
            int nIndex = nextInt(drbg, upper - lower + 1) + lower;

            if (bitSet.get(nIndex)) {
                if ((nIndex & 1) != 0) {
                    while (bitSet.get(upper)) {
                        upper--;
                    }

                    nIndex = upper--;
                } else {
                    while (bitSet.get(lower)) {
                        lower++;
                    }

                    nIndex = lower++;
                }
            }

            bitSet.set(nIndex);
        }

        return bitSet;
    }

    // the classic unbiased sampler
    private int nextInt(SP80090DRBG drbg, int range) {
        if ((range & -range) == range) // i.e., range is a power of 2
        {
            return (int) ((range * (long) makePositiveInt(drbg)) >> 31);
        }

        int bits, val;
        do {
            bits = makePositiveInt(drbg);
            val = bits % range;
        } while (bits - val + (range - 1) < 0);

        return val;
    }

    private int makePositiveInt(SP80090DRBG drbg) {
        byte[] bytes = new byte[4];

        drbg.generate(bytes, null, false);

        return ((bytes[0] & 0x7f) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff);
    }

    private class SingleEntropySourceProvider implements EntropySourceProvider {
        private final byte[] data;

        protected SingleEntropySourceProvider(byte[] data) {
            if (data == null) {
                throw new IllegalArgumentException("No challenge seed available to seeded challenger.");
            }

            this.data = data;
        }

        public EntropySource get(final int bitsRequired) {
            return new EntropySource() {
                int index = 0;

                public boolean isPredictionResistant() {
                    return true;
                }

                public byte[] getEntropy() {
                    byte[] rv = new byte[bitsRequired / 8];

                    if (data.length < (index + rv.length)) {
                        throw new IllegalStateException(
                                "Insufficient entropy - need " + rv.length + " bytes for challenge seed.");
                    }

                    System.arraycopy(data, index, rv, 0, rv.length);

                    index += bitsRequired / 8;

                    return rv;
                }

                public int entropySize() {
                    return bitsRequired;
                }
            };
        }
    }
}