1 package org.bouncycastle.crypto.digests;
2 
3 import org.bouncycastle.crypto.DataLengthException;
4 import org.bouncycastle.crypto.Digest;
5 import org.bouncycastle.crypto.Xof;
6 import org.bouncycastle.util.Arrays;
7 import org.bouncycastle.util.Strings;
8 
9 /**
10  * ParallelHash - a hash designed  to  support the efficient hashing of very long strings, by taking advantage
11  * of the parallelism available in modern processors with an optional XOF mode.
12  * <p>
13  * From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash
14  * </p>
15  */
16 public class ParallelHash
17     implements Xof, Digest
18 {
19     private static final byte[] N_PARALLEL_HASH = Strings.toByteArray("ParallelHash");
20 
21     private final CSHAKEDigest cshake;
22     private final CSHAKEDigest compressor;
23     private final int bitLength;
24     private final int outputLength;
25     private final int B;
26     private final byte[] buffer;
27     private final byte[] compressorBuffer;
28 
29     private boolean firstOutput;
30     private int nCount;
31     private int bufOff;
32 
33     /**
34      * Base constructor.
35      *
36      * @param bitLength bit length of the underlying SHAKE function, 128 or 256.
37      * @param S the customization string - available for local use.
38      * @param B the blocksize (in bytes) for hashing.
39      */
ParallelHash(int bitLength, byte[] S, int B)40     public ParallelHash(int bitLength, byte[] S, int B)
41     {
42         this(bitLength, S, B, bitLength * 2);
43     }
44 
ParallelHash(int bitLength, byte[] S, int B, int outputSize)45     public ParallelHash(int bitLength, byte[] S, int B, int outputSize)
46     {
47         this.cshake = new CSHAKEDigest(bitLength, N_PARALLEL_HASH, S);
48         this.compressor = new CSHAKEDigest(bitLength, new byte[0], new byte[0]);
49         this.bitLength = bitLength;
50         this.B = B;
51         this.outputLength = (outputSize + 7) / 8;
52         this.buffer = new byte[B];
53         this.compressorBuffer = new byte[bitLength * 2 / 8];
54 
55         reset();
56     }
57 
ParallelHash(ParallelHash source)58     public ParallelHash(ParallelHash source)
59     {
60         this.cshake = new CSHAKEDigest(source.cshake);
61         this.compressor = new CSHAKEDigest(source.compressor);
62         this.bitLength = source.bitLength;
63         this.B = source.B;
64         this.outputLength = source.outputLength;
65         this.buffer = Arrays.clone(source.buffer);
66         this.compressorBuffer = Arrays.clone(source.compressorBuffer);
67     }
68 
getAlgorithmName()69     public String getAlgorithmName()
70     {
71         return "ParallelHash" + cshake.getAlgorithmName().substring(6);
72     }
73 
getByteLength()74     public int getByteLength()
75     {
76         return cshake.getByteLength();
77     }
78 
getDigestSize()79     public int getDigestSize()
80     {
81         return outputLength;
82     }
83 
update(byte in)84     public void update(byte in)
85         throws IllegalStateException
86     {
87         buffer[bufOff++] = in;
88         if (bufOff == buffer.length)
89         {
90             compress();
91         }
92     }
93 
update(byte[] in, int inOff, int len)94     public void update(byte[] in, int inOff, int len)
95         throws DataLengthException, IllegalStateException
96     {
97         len = Math.max(0,  len);
98 
99         //
100         // fill the current word
101         //
102         int i = 0;
103         if (bufOff != 0)
104         {
105             while (i < len && bufOff != buffer.length)
106             {
107                 buffer[bufOff++] = in[inOff + i++];
108             }
109 
110             if (bufOff == buffer.length)
111             {
112                 compress();
113             }
114         }
115 
116         if (i < len)
117         {
118             while (len - i > B)
119             {
120                 compress(in, inOff + i, B);
121                 i += B;
122             }
123         }
124 
125         while (i < len)
126         {
127             update(in[inOff + i++]);
128         }
129     }
130 
compress()131     private void compress()
132     {
133         compress(buffer, 0, bufOff);
134         bufOff = 0;
135     }
136 
compress(byte[] buf, int offSet, int len)137     private void compress(byte[] buf, int offSet, int len)
138     {
139         compressor.update(buf, offSet, len);
140         compressor.doFinal(compressorBuffer, 0, compressorBuffer.length);
141 
142         cshake.update(compressorBuffer, 0, compressorBuffer.length);
143 
144         nCount++;
145     }
146 
wrapUp(int outputSize)147     private void wrapUp(int outputSize)
148     {
149         if (bufOff != 0)
150         {
151             compress();
152         }
153         byte[] nOut = XofUtils.rightEncode(nCount);
154         byte[] encOut = XofUtils.rightEncode(outputSize * 8);
155 
156         cshake.update(nOut, 0, nOut.length);
157         cshake.update(encOut, 0, encOut.length);
158 
159         firstOutput = false;
160     }
161 
doFinal(byte[] out, int outOff)162     public int doFinal(byte[] out, int outOff)
163         throws DataLengthException, IllegalStateException
164     {
165         if (firstOutput)
166         {
167             wrapUp(outputLength);
168         }
169 
170         int rv = cshake.doFinal(out, outOff, getDigestSize());
171 
172         reset();
173 
174         return rv;
175     }
176 
doFinal(byte[] out, int outOff, int outLen)177     public int doFinal(byte[] out, int outOff, int outLen)
178     {
179         if (firstOutput)
180         {
181             wrapUp(outputLength);
182         }
183 
184         int rv = cshake.doFinal(out, outOff, outLen);
185 
186         reset();
187 
188         return rv;
189     }
190 
doOutput(byte[] out, int outOff, int outLen)191     public int doOutput(byte[] out, int outOff, int outLen)
192     {
193         if (firstOutput)
194         {
195             wrapUp(0);
196         }
197 
198         return cshake.doOutput(out, outOff, outLen);
199     }
200 
reset()201     public void reset()
202     {
203         cshake.reset();
204         Arrays.clear(buffer);
205 
206         byte[] hdr = XofUtils.leftEncode(B);
207         cshake.update(hdr, 0, hdr.length);
208 
209         nCount = 0;
210         bufOff = 0;
211         firstOutput = true;
212     }
213 }
214