View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
18  
19  import org.bouncycastle.crypto.ExtendedDigest;
20  import org.bouncycastle.util.Memoable;
21  
22  import java.util.Arrays;
23  
24  /**
25   * CubeHash Digest.
26   */
27  @SuppressWarnings("checkstyle:MagicNumber")
28  public class GordianCubeHashDigest
29          implements ExtendedDigest, Memoable {
30      /**
31       * The State length.
32       */
33      private static final int STATELEN = 32;
34  
35      /**
36       * The Swap length.
37       */
38      private static final int SWAPLEN = STATELEN / 2;
39  
40      /**
41       * The state.
42       */
43      private final int[] theState = new int[STATELEN];
44  
45      /**
46       * The swap buffer.
47       */
48      private final int[] theSwap = new int[SWAPLEN];
49  
50      /**
51       * The initial state.
52       */
53      private final int[] theInitState;
54  
55      /**
56       * The block length.
57       */
58      private final int theBlockLen;
59  
60      /**
61       * The hash length.
62       */
63      private final int theHashLen;
64  
65      /**
66       * The number of final rounds.
67       */
68      private final int theNumRounds;
69  
70      /**
71       * The number of final rounds.
72       */
73      private final int theNumFinalRounds;
74  
75      /**
76       * The input buffer.
77       */
78      private final byte[] theInputBuffer;
79  
80      /**
81       * The current byte index into input buffer.
82       */
83      private int theByteIndex;
84  
85      /**
86       * Constructor.
87       *
88       * @param pHashLen the hash lengyh
89       */
90      public GordianCubeHashDigest(final int pHashLen) {
91          this(pHashLen, 32, 16, 16, 32);
92      }
93  
94      /**
95       * Constructor.
96       *
97       * @param pSource the source digest
98       */
99      private GordianCubeHashDigest(final GordianCubeHashDigest pSource) {
100         /* Store configuration */
101         theHashLen = pSource.theHashLen;
102         theBlockLen = pSource.theBlockLen;
103         theNumRounds = pSource.theNumRounds;
104         theNumFinalRounds = pSource.theNumFinalRounds;
105 
106         /* Create the input buffer */
107         theInputBuffer = new byte[theBlockLen];
108 
109         /* Copy the state */
110         System.arraycopy(pSource.theState, 0, theState, 0, theState.length);
111         System.arraycopy(pSource.theInputBuffer, 0, theInputBuffer, 0, theBlockLen);
112         theByteIndex = pSource.theByteIndex;
113         theInitState = Arrays.copyOf(pSource.theInitState, theState.length);
114     }
115 
116     /**
117      * Constructor.
118      *
119      * @param pHashLen     the hashLen in bits
120      * @param pBlockLen    the blockLen in bytes
121      * @param pNumRounds   the number of rounds per block
122      * @param pInitRounds  the number of initial rounds
123      * @param pFinalRounds the number of final rounds;
124      */
125     private GordianCubeHashDigest(final int pHashLen,
126                                   final int pBlockLen,
127                                   final int pNumRounds,
128                                   final int pInitRounds,
129                                   final int pFinalRounds) {
130         /* Store configuration */
131         theHashLen = pHashLen / Byte.SIZE;
132         theBlockLen = pBlockLen;
133         theNumRounds = pNumRounds;
134         theNumFinalRounds = pFinalRounds;
135 
136         /* Create the input buffer */
137         theInputBuffer = new byte[pBlockLen];
138 
139         /* Initialise the state */
140         theState[0] = theHashLen;
141         theState[1] = pBlockLen;
142         theState[2] = pNumRounds;
143         performRounds(pInitRounds);
144 
145         /* Save the initial state */
146         theInitState = Arrays.copyOf(theState, theState.length);
147     }
148 
149     @Override
150     public String getAlgorithmName() {
151         return "CubeHash-" + theHashLen * Byte.SIZE;
152     }
153 
154     @Override
155     public int getDigestSize() {
156         return theHashLen;
157     }
158 
159     @Override
160     public int getByteLength() {
161         return theHashLen;
162     }
163 
164     @Override
165     public void update(final byte pByte) {
166         theInputBuffer[theByteIndex++] = pByte;
167         if (theByteIndex == theBlockLen) {
168             processBlock();
169             theByteIndex = 0;
170         }
171     }
172 
173     @Override
174     public void update(final byte[] pData, final int pOffset, final int pLength) {
175         for (int i = 0; i < pLength; i++) {
176             update(pData[pOffset + i]);
177         }
178     }
179 
180     @Override
181     public int doFinal(final byte[] pHash, final int pOffset) {
182         finaliseHash();
183         outputHash(pHash, pOffset);
184         return getDigestSize();
185     }
186 
187     @Override
188     public void reset() {
189         System.arraycopy(theInitState, 0, theState, 0, theState.length);
190         theByteIndex = 0;
191     }
192 
193     @Override
194     public GordianCubeHashDigest copy() {
195         return new GordianCubeHashDigest(this);
196     }
197 
198     @Override
199     public void reset(final Memoable pState) {
200         final GordianCubeHashDigest d = (GordianCubeHashDigest) pState;
201 
202         /* Copy the state */
203         System.arraycopy(d.theState, 0, theState, 0, theState.length);
204         System.arraycopy(d.theInputBuffer, 0, theInputBuffer, 0, theBlockLen);
205         theByteIndex = d.theByteIndex;
206     }
207 
208     /**
209      * Decode a 32-bit value from a buffer (little-endian).
210      *
211      * @param buf the input buffer
212      * @param off the input offset
213      * @return the decoded value
214      */
215     private static int decode32le(final byte[] buf,
216                                   final int off) {
217         return (buf[off] & 0xFF)
218                 | ((buf[off + 1] & 0xFF) << 8)
219                 | ((buf[off + 2] & 0xFF) << 16)
220                 | ((buf[off + 3] & 0xFF) << 24);
221     }
222 
223     /**
224      * Encode a 32-bit value into a buffer (little-endian).
225      *
226      * @param val the value to encode
227      * @param buf the output buffer
228      * @param off the output offset
229      */
230     private static void encode32le(final int val,
231                                    final byte[] buf,
232                                    final int off) {
233         buf[off] = (byte) val;
234         buf[off + 1] = (byte) (val >> 8);
235         buf[off + 2] = (byte) (val >> 16);
236         buf[off + 3] = (byte) (val >> 24);
237     }
238 
239     /**
240      * Process a block.
241      */
242     private void processBlock() {
243         /* Loop through the bytes in the block */
244         for (int i = 0, j = 0; j < theBlockLen; i++, j += Integer.BYTES) {
245             theState[i] ^= decode32le(theInputBuffer, j);
246         }
247 
248         /* Perform the required rounds */
249         performRounds(theNumRounds);
250     }
251 
252     /**
253      * Finalise the hash.
254      */
255     private void finaliseHash() {
256         /* Set the marker */
257         theInputBuffer[theByteIndex++] = (byte) 0x80;
258 
259         /* Fill remainder of buffer with zeroes */
260         while (theByteIndex < theBlockLen) {
261             theInputBuffer[theByteIndex++] = 0;
262         }
263 
264         /* Process the block */
265         processBlock();
266 
267         /* Adjust the final State word */
268         theState[STATELEN - 1] ^= 1;
269 
270         /* Perform the required rounds */
271         performRounds(theNumFinalRounds);
272     }
273 
274     /**
275      * Output the hash.
276      *
277      * @param pOutput the output buffer
278      * @param pOffSet the offset with the output buffer to write to
279      */
280     private void outputHash(final byte[] pOutput,
281                             final int pOffSet) {
282         /* Loop through the bytes in the block */
283         for (int i = 0, j = 0; j < theHashLen; i++, j += Integer.BYTES) {
284             encode32le(theState[i], pOutput, j + pOffSet);
285         }
286 
287         /* Reset back to initial state */
288         reset();
289     }
290 
291     /**
292      * Perform the required number of rounds.
293      *
294      * @param pNumRounds the number of rounds
295      */
296     private void performRounds(final int pNumRounds) {
297         /* Loop to perform the round */
298         for (int i = 0; i < pNumRounds; i++) {
299             performRound();
300         }
301     }
302 
303     /**
304      * Perform the round.
305      */
306     private void performRound() {
307         /* 1. Add x[0jklm] into x[1jklm] modulo 2^32, for each (j,k,l,m) */
308         for (int i = 0; i < SWAPLEN; i++) {
309             theState[i + SWAPLEN] += theState[i];
310         }
311 
312         /* 2. Rotate x[0jklm] upwards by 7 bits, for each (j,k,l,m) */
313         for (int i = 0; i < SWAPLEN; i++) {
314             theSwap[i] = theState[i] << 7 | theState[i] >>> 25;
315         }
316 
317         /* 3. Swap x[00klm] with x[01klm], for each (k,l,m) */
318         for (int i = 0; i < SWAPLEN; i++) {
319             theState[i] = theSwap[i ^ 8];
320         }
321 
322         /* 4. Xor x[1jklm] into x[0jklm], for each (j,k,l,m) */
323         for (int i = 0; i < SWAPLEN; i++) {
324             theState[i] ^= theState[i + SWAPLEN];
325         }
326 
327         /* 5. Swap x[1jk0m] with x[1jk1m], for each (j,k,m) */
328         for (int i = 0; i < SWAPLEN; i++) {
329             theSwap[i] = theState[i + SWAPLEN];
330         }
331         for (int i = 0; i < SWAPLEN; i++) {
332             theState[i + SWAPLEN] = theSwap[i ^ 2];
333         }
334 
335         /* 6. Add x[0jklm] into x[1jklm] modulo 2^32, for each (j,k,l,m) */
336         for (int i = 0; i < SWAPLEN; i++) {
337             theState[i + SWAPLEN] += theState[i];
338         }
339 
340         /* 7. Rotate x[0jklm] upwards by 11 bits, for each (j,k,l,m) */
341         for (int i = 0; i < SWAPLEN; i++) {
342             theSwap[i] = theState[i] << 11 | theState[i] >>> 21;
343         }
344 
345         /* 8. Swap x[0j0lm] with x[0j1lm], for each (j,l,m) */
346         for (int i = 0; i < SWAPLEN; i++) {
347             theState[i] = theSwap[i ^ 4];
348         }
349 
350         /* 9. Xor x[1jklm] into x[0jklm], for each (j,k,l,m) */
351         for (int i = 0; i < SWAPLEN; i++) {
352             theState[i] ^= theState[i + SWAPLEN];
353         }
354 
355         /* 10. Swap x[1jkl0] with x[1jkl1], for each (j,k,l) */
356         for (int i = 0; i < SWAPLEN; i++) {
357             theSwap[i] = theState[i + SWAPLEN];
358         }
359         for (int i = 0; i < SWAPLEN; i++) {
360             theState[i + SWAPLEN] = theSwap[i ^ 1];
361         }
362     }
363 }