package se.chalmers.gdcn.hashcash; import javax.crypto.Mac; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.BitSet; import java.util.Random; /** * Created by Leif on 2014-03-29. * */ public class Challenge implements Serializable { public final HashCash.Purpose purpose; private final byte[] seed, mac; public final int difficulty; Challenge(byte[] seed, int difficulty, byte[] mac) throws InvalidKeyException { this.purpose = HashCash.Purpose.NONE; this.seed = seed.clone(); this.difficulty = difficulty; this.mac = mac.clone(); } byte[] getMAC() { return mac.clone(); } /** * Creates a new anonymous Challenge. * @param seed The seed of the Challenge. Should not be reused! * @param difficulty The difficulty of the Challenge. * @param key The key which should be used to authenticate the solution. (Must be compatible with javax.crypto.Mac) */ public Challenge(byte[] seed, int difficulty, Key key) throws InvalidKeyException { this(HashCash.Purpose.NONE,seed,difficulty,key); } /** * Creates a new identifiable Challenge. * @param purpose The purpose of the Challenge. * @param seed The seed of the Challenge. Should not be reused! * @param difficulty The difficulty of the Challenge. * @param key The key which should be used to authenticate the solution. (Must be compatible with javax.crypto.Mac) */ public Challenge(HashCash.Purpose purpose, byte[] seed, int difficulty, Key key) throws InvalidKeyException { this.purpose = purpose; this.seed = seed.clone(); this.difficulty = difficulty; this.mac = generateMAC(key); } /** * Gets the identity of the challenge * @return The identity if it exists, null otherwise. */ public HashCash.Purpose getPurpose() { return purpose; } private byte[] generateMAC(Key key) throws InvalidKeyException { Mac macGen = null; try { macGen = Mac.getInstance(key.getAlgorithm()); macGen.init(key); macGen.update(purpose.toString().getBytes("UTF-8")); } catch (InvalidKeyException e) { e.printStackTrace(); //TODO Something went really wrong here, present it to the user in some way? } catch (NoSuchAlgorithmException e) { throw new InvalidKeyException("The key must be compatible with javax.crypto.Mac"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); //TODO Tell the user that UTF-8 is needed. } macGen.update(seed); return macGen.doFinal(ByteBuffer.allocate(4).putInt(difficulty).array()); } public boolean isAuthentic(Key key) throws InvalidKeyException { return Arrays.equals(this.mac, generateMAC(key)); } public byte[] getSeed() { return seed.clone(); } /** * Solves the Challenge. * @return A solution such that this.solved(solution) == true */ public Solution solve() { Random random = new Random(); byte[] testToken; do { testToken = new byte[random.nextInt(32)]; random.nextBytes(testToken); } while (!isCorrectToken(testToken)); return new Solution(testToken,this); } @Override public String toString() { return "Challenge{" + "difficulty='" + difficulty +"'"+ '}'; } static byte[] hash(byte[] seed, byte[] token) { try { MessageDigest md = MessageDigest.getInstance(HashCash.HASH_ALGORITHM); md.update(seed); md.update(token); return md.digest(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); //TODO Something went really wrong here, present it to the user in some way? } return null; } /** * Checks if the given token solves the challenge * @param token The token to be checked against the Challenge. * @return True if the token is a solution to the challenge, false otherwise. */ public boolean isCorrectToken(byte[] token) { return checkZeros(hash(seed,token)); } boolean checkZeros(byte[] hash) { BitSet hashBits = BitSet.valueOf(hash); BitSet zeros = new BitSet(difficulty); zeros.clear(); zeros.or(hashBits); return zeros.get(0,difficulty-1).isEmpty(); } }