diff options
5 files changed, 213 insertions, 1 deletions
diff --git a/src/main/java/org/bouncycastle/crypto/Commitment.java b/src/main/java/org/bouncycastle/crypto/Commitment.java new file mode 100644 index 00000000..f1dc05a3 --- /dev/null +++ b/src/main/java/org/bouncycastle/crypto/Commitment.java @@ -0,0 +1,42 @@ +package org.bouncycastle.crypto; + +/** + * General holding class for a commitment. + */ +public class Commitment +{ + private final byte[] secret; + private final byte[] commitment; + + /** + * Base constructor. + * + * @param secret an encoding of the secret required to reveal the commitment. + * @param commitment an encoding of the sealed commitment. + */ + public Commitment(byte[] secret, byte[] commitment) + { + this.secret = secret; + this.commitment = commitment; + } + + /** + * The secret required to reveal the commitment. + * + * @return an encoding of the secret associated with the commitment. + */ + public byte[] getSecret() + { + return secret; + } + + /** + * The sealed commitment. + * + * @return an encoding of the sealed commitment. + */ + public byte[] getCommitment() + { + return commitment; + } +} diff --git a/src/main/java/org/bouncycastle/crypto/Committer.java b/src/main/java/org/bouncycastle/crypto/Committer.java new file mode 100644 index 00000000..5c93e5d1 --- /dev/null +++ b/src/main/java/org/bouncycastle/crypto/Committer.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto; + +/** + * General interface fdr classes that produce and validate commitments. + */ +public interface Committer +{ + /** + * Generate a commitment for the passed in message. + * + * @param message the message to be committed to, + * @return a Commitment + */ + Commitment commit(byte[] message); + + /** + * Return true if the passed in commitment represents a commitment to the passed in maessage. + * + * @param commitment a commitment previously generated. + * @param message the message that was expected to have been committed to. + * @return true if commitment matches message, false otherwise. + */ + boolean isRevealed(Commitment commitment, byte[] message); +} diff --git a/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java b/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java new file mode 100644 index 00000000..1494c3ce --- /dev/null +++ b/src/main/java/org/bouncycastle/crypto/commitments/HashCommitter.java @@ -0,0 +1,81 @@ +package org.bouncycastle.crypto.commitments; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Commitment; +import org.bouncycastle.crypto.Committer; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; + +/** + * A basic hash-committer as described in "Making Mix Nets Robust for Electronic Voting by Randomized Partial Checking", + * by Jakobsson, Juels, and Rivest (11th Usenix Security Symposium, 2002). + */ +public class HashCommitter + implements Committer +{ + private final Digest digest; + private final int byteLength; + private final SecureRandom random; + + /** + * Base Constructor. The maximum message length that can be committed to is half the length of the internal + * block size for the digest (ExtendedDigest.getBlockLength()). + * + * @param digest digest to use for creating commitments. + * @param random source of randomness for generating secrets. + */ + public HashCommitter(ExtendedDigest digest, SecureRandom random) + { + this.digest = digest; + this.byteLength = digest.getByteLength(); + this.random = random; + } + + /** + * Generate a commitment for the passed in message. + * + * @param message the message to be committed to, + * @return a Commitment + */ + public Commitment commit(byte[] message) + { + if (message.length > byteLength / 2) + { + throw new DataLengthException("Message to be committed to too large for digest."); + } + + byte[] w = new byte[byteLength - message.length]; + + random.nextBytes(w); + + return new Commitment(w, calculateCommitment(w, message)); + } + + /** + * Return true if the passed in commitment represents a commitment to the passed in maessage. + * + * @param commitment a commitment previously generated. + * @param message the message that was expected to have been committed to. + * @return true if commitment matches message, false otherwise. + */ + public boolean isRevealed(Commitment commitment, byte[] message) + { + byte[] calcCommitment = calculateCommitment(commitment.getSecret(), message); + + return Arrays.constantTimeAreEqual(commitment.getCommitment(), calcCommitment); + } + + private byte[] calculateCommitment(byte[] w, byte[] message) + { + byte[] commitment = new byte[digest.getDigestSize()]; + + digest.update(w, 0, w.length); + digest.update(message, 0, message.length); + digest.doFinal(commitment, 0); + + return commitment; + } +} diff --git a/src/test/java/org/bouncycastle/crypto/test/HashCommitmentTest.java b/src/test/java/org/bouncycastle/crypto/test/HashCommitmentTest.java new file mode 100644 index 00000000..d4bd83de --- /dev/null +++ b/src/test/java/org/bouncycastle/crypto/test/HashCommitmentTest.java @@ -0,0 +1,64 @@ +package org.bouncycastle.crypto.test; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.Commitment; +import org.bouncycastle.crypto.Committer; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.commitments.HashCommitter; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +public class HashCommitmentTest + extends SimpleTest +{ + public String getName() + { + return "HashCommitmentTest"; + } + + public void performTest() + throws Exception + { + byte[] data = Hex.decode("4e6f77206973207468652074696d6520666f7220616c6c20"); + + Committer committer = new HashCommitter(new SHA256Digest(), new SecureRandom()); + + Commitment c = committer.commit(data); + + committer = new HashCommitter(new SHA256Digest(), new SecureRandom()); + + if (!committer.isRevealed(c, data)) + { + fail("commitment failed to validate"); + } + + committer = new HashCommitter(new SHA1Digest(), new SecureRandom()); + + if (committer.isRevealed(c, data)) + { + fail("commitment validated!!"); + } + + // SHA1 has a block size of 512 bits, try a message that's too big + + try + { + c = committer.commit(new byte[33]); + } + catch (DataLengthException e) + { + if (!e.getMessage().equals("Message to be committed to too large for digest.")) + { + fail("exception thrown but wrong message"); + } + } + } + + public static void main(String[] args) + { + runTest(new HashCommitmentTest()); + } +} diff --git a/src/test/java/org/bouncycastle/crypto/test/RegressionTest.java b/src/test/java/org/bouncycastle/crypto/test/RegressionTest.java index 906eb910..7f3732d6 100644 --- a/src/test/java/org/bouncycastle/crypto/test/RegressionTest.java +++ b/src/test/java/org/bouncycastle/crypto/test/RegressionTest.java @@ -116,7 +116,8 @@ public class RegressionTest new OCBTest(), new NonMemoableDigestTest(), new RSAKeyEncapsulationTest(), - new ECIESKeyEncapsulationTest() + new ECIESKeyEncapsulationTest(), + new HashCommitmentTest() }; public static void main( |