package games.strategy.engine.random;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.framework.IGame;
import games.strategy.engine.framework.ServerGame;
import games.strategy.engine.vault.Vault;
import games.strategy.engine.vault.VaultID;
/**
* A random source that generates numbers using a secure algorithm shared
* between two players.
* Code originally contributed by Ben Giddings.
*/
public class CryptoRandomSource implements IRandomSource {
private final IRandomSource m_plainRandom = new PlainRandomSource();
/**
* converts an int[] to a byte[].
*/
public static byte[] intsToBytes(final int[] ints) {
final byte[] rVal = new byte[ints.length * 4];
for (int i = 0; i < ints.length; i++) {
rVal[4 * i] = (byte) (0x000000FF & ints[i]);
rVal[(4 * i) + 1] = (byte) ((0x000000FF & (ints[i] >> 8)));
rVal[(4 * i) + 2] = (byte) ((0x000000FF & (ints[i] >> 16)));
rVal[(4 * i) + 3] = (byte) ((0x000000FF & (ints[i] >> 24)));
}
return rVal;
}
static int byteToIntUnsigned(final byte val) {
return val & 0xff;
}
public static int[] bytesToInts(final byte[] bytes) {
final int[] rVal = new int[bytes.length / 4];
for (int i = 0; i < rVal.length; i++) {
rVal[i] = byteToIntUnsigned(bytes[4 * i]) + (byteToIntUnsigned(bytes[4 * i + 1]) << 8)
+ (byteToIntUnsigned(bytes[4 * i + 2]) << 16) + (byteToIntUnsigned(bytes[4 * i + 3]) << 24);
}
return rVal;
}
public static int[] xor(final int[] val1, final int[] val2, final int max) {
if (val1.length != val2.length) {
throw new IllegalArgumentException("Arrays not of same length");
}
final int[] rVal = new int[val1.length];
for (int i = 0; i < val1.length; i++) {
rVal[i] = (val1[i] + val2[i]) % max;
}
return rVal;
}
// the remote players who involved in rolling the dice
// dice are rolled securly between us and her
private final PlayerID m_remotePlayer;
private final IGame m_game;
public CryptoRandomSource(final PlayerID remotePlayer, final IGame game) {
m_remotePlayer = remotePlayer;
m_game = game;
}
/**
* All delegates should use random data that comes from both players so that
* neither player cheats.
*/
@Override
public int getRandom(final int max, final String annotation) throws IllegalArgumentException, IllegalStateException {
return getRandom(max, 1, annotation)[0];
}
/**
* Delegates should not use random data that comes from any other source.
*/
@Override
public int[] getRandom(final int max, final int count, final String annotation)
throws IllegalArgumentException, IllegalStateException {
if (count <= 0) {
throw new IllegalArgumentException("Invalid count:" + count);
}
final Vault vault = m_game.getVault();
// generate numbers locally, and put them in the vault
final int[] localRandom = m_plainRandom.getRandom(max, count, annotation);
// lock it so the client knows that its there, but cant read it
final VaultID localID = vault.lock(intsToBytes(localRandom));
// ask the remote to generate numbers
final IRemoteRandom remote =
(IRemoteRandom) (m_game.getRemoteMessenger().getRemote(ServerGame.getRemoteRandomName(m_remotePlayer)));
final Object clientRandom = remote.generate(max, count, annotation, localID);
if (!(clientRandom instanceof int[])) {
// Let the error be thrown
System.out.println("Client remote random generated: " + clientRandom + ". Asked for: " + count + "x" + max
+ " for " + annotation);
}
final int[] remoteNumbers = (int[]) clientRandom;
// unlock ours, tell the client he can verify
vault.unlock(localID);
remote.verifyNumbers();
// finally, we join the two together to get the real value
return xor(localRandom, remoteNumbers, max);
}
}