aboutsummaryrefslogtreecommitdiffstats
path: root/libraries/spongycastle/core/src/main/java/org/spongycastle/crypto/commitments/HashCommitter.java
blob: 4320d86d3f05563db8ecd4ea5e29844eea0b8341 (plain)
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
package org.spongycastle.crypto.commitments;

import java.security.SecureRandom;

import org.spongycastle.crypto.Commitment;
import org.spongycastle.crypto.Committer;
import org.spongycastle.crypto.DataLengthException;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.ExtendedDigest;
import org.spongycastle.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).
 * <p>
 * Use this class if you can enforce fixed length for messages. If you need something more general, use the GeneralHashCommitter.
 * </p>
 */
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 message.
     *
     * @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)
    {
        if (message.length + commitment.getSecret().length != byteLength)
        {
            throw new DataLengthException("Message and witness secret lengths do not match.");
        }

        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;
    }
}