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 io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianBlake2Parameters;
20  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianBlake2Parameters.GordianBlake2ParametersBuilder;
21  import org.bouncycastle.crypto.DataLengthException;
22  import org.bouncycastle.crypto.Digest;
23  import org.bouncycastle.crypto.OutputLengthException;
24  import org.bouncycastle.util.Arrays;
25  
26  import java.util.Enumeration;
27  import java.util.NoSuchElementException;
28  
29  /**
30   * Blake2 Tree Hash.
31   * <p>This implementation will support all elements of Blake2Parameters except for Maximum OutputLength.
32   * This is restricted to the output length of the underlying digest.
33   * <p>Tree configuration is required and defaults to unlimited fanOut, maxDepth of 2 and 4K leafLength.
34   * <p>The implementation consumes a large message as a standard digest and builds the lowest level of the tree on the fly.
35   * On the {@link #doFinal(byte[], int)} call, the remainder of the tree is calculated and the topmost tree node is returned as the hash.
36   * This tree is retained until an explicit {@link #reset()} is called. Further update calls are disabled while the tree is retained.
37   * <p>While the tree is retained, each leaf may be explicitly replaced via {@link #updateLeaf(int, byte[], int, int)}, leading to a recalculation
38   * of the tree node which may be obtained via {@link #obtainResult(byte[], int)}.
39   * <p>The last leaf at the bottom level of the tree can be any length from 1 to leafLen. It may be replaced by data that is between 1 to leafLen using the
40   * {@link #updateLeaf(int, byte[], int, int)} method where the new length does not have to be the same as the old length.
41   * Other leaves must be replaced by data of length leafLen.
42   * <p>The number of leaves cannot be increased/decreased once the tree has been built. If the length of data is changed, a new tree should be built.
43   * <p>TODO
44   * <ul>
45   * <li>Replacing a leaf should not automatically trigger a recalculation of the tree. Instead the updated leaves should be left in an update list.
46   * This update list will be processed only when the final result is requested, allowing several leaves to be replaced before the tree is recalculated.
47   * <li>A duplicate leaf in the update list will replace the earlier leaf.
48   * <li>On recalculation the updateList will be validated. The tree can be extended if all additional leaves are present in the update list and the previously last
49   * leaf was either leafLen long or is now present as a full leaf in the update list. Only the last leaf may be shorter than the leafLen.
50   * <li>A new call should be provided to truncate the tree at a total length. This must be shorter than the current length and will form part of the update list
51   * to be processed when calculating the result.
52   * </ul>
53   */
54  public class GordianBlake2Tree
55          implements Digest {
56      /**
57       * Default leaf length.
58       */
59      private static final int DEFAULT_LEAFLEN = 4096;
60  
61      /**
62       * The underlying Blake2 instance.
63       */
64      private final GordianBlake2Base theDigest;
65  
66      /**
67       * The TreeStore.
68       */
69      private final GordianBlake2TreeStore theStore;
70  
71      /**
72       * The single byte buffer.
73       */
74      private final byte[] singleByte = new byte[1];
75  
76      /**
77       * Hash buffer.
78       */
79      private final byte[] theHash;
80  
81      /**
82       * The data processed on current leaf.
83       */
84      private int theProcessed;
85  
86      /**
87       * Constructor.
88       *
89       * @param pDigest the underlying digest.
90       */
91      public GordianBlake2Tree(final GordianBlake2Base pDigest) {
92          /* Store parameters and initialise store */
93          theDigest = pDigest;
94          theStore = new GordianBlake2TreeStore(pDigest);
95          theHash = new byte[theDigest.getDigestSize()];
96  
97          /* Initialise to default values */
98          final GordianBlake2ParametersBuilder myBuilder = new GordianBlake2ParametersBuilder();
99          myBuilder.setTreeConfig(0, 2, DEFAULT_LEAFLEN);
100         init(myBuilder.build());
101     }
102 
103     @Override
104     public String getAlgorithmName() {
105         return theDigest.getAlgorithmName() + "Tree";
106     }
107 
108     @Override
109     public int getDigestSize() {
110         return theDigest.getDigestSize();
111     }
112 
113     @Override
114     public void update(final byte pIn) {
115         singleByte[0] = pIn;
116         update(singleByte, 0, 1);
117     }
118 
119     @Override
120     public void update(final byte[] pIn,
121                        final int pInOff,
122                        final int pLen) {
123         processData(pIn, pInOff, pLen);
124     }
125 
126     @Override
127     public int doFinal(final byte[] pOut,
128                        final int pOutOffset) {
129         /* Mark the entry as the last node */
130         theDigest.setLastNode();
131 
132         /* Finalise the leaf and process the result */
133         theDigest.doFinal(theHash, 0);
134         theStore.addElement(Arrays.clone(theHash));
135         return theStore.calculateTree(pOut, pOutOffset);
136     }
137 
138     @Override
139     public void reset() {
140         theDigest.setNodePosition(0, 0);
141         theProcessed = 0;
142         theStore.reset();
143     }
144 
145     /**
146      * Obtain the tree result.
147      *
148      * @param pOut       the output buffer
149      * @param pOutOffset the offset into the output buffer
150      * @return the number of bytes returned
151      */
152     public int obtainResult(final byte[] pOut,
153                             final int pOutOffset) {
154         /* Access result from store */
155         return theStore.obtainResult(pOut, pOutOffset);
156     }
157 
158     /**
159      * Obtain the leaf length.
160      *
161      * @return the leafLength.
162      */
163     public int getLeafLen() {
164         return theDigest.getLeafLen();
165     }
166 
167     /**
168      * Process data.
169      *
170      * @param pIn       the input buffer
171      * @param pInOffSet the starting offset in the input buffer
172      * @param pLen      the length of data to process
173      */
174     private void processData(final byte[] pIn,
175                              final int pInOffSet,
176                              final int pLen) {
177         /* Cannot process further data once tree is built */
178         if (theStore.treeBuilt()) {
179             throw new IllegalStateException("Tree has been built");
180         }
181 
182         /* Determine space in current block */
183         final int blkSize = getLeafLen();
184         final int mySpace = blkSize - theProcessed;
185 
186         /* If all data can be processed by the current leaf */
187         if (mySpace >= pLen) {
188             /* Update and return */
189             theDigest.update(pIn, pInOffSet, pLen);
190             theProcessed += pLen;
191             return;
192         }
193 
194         /* Process as much as possible into current leaf */
195         if (mySpace > 0) {
196             theDigest.update(pIn, pInOffSet, mySpace);
197             theProcessed += mySpace;
198         }
199 
200         /* Loop while we have data remaining */
201         int myProcessed = mySpace;
202         while (myProcessed < pLen) {
203             /* If the current leaf is full */
204             if (theProcessed == blkSize) {
205                 /* Finalise the leaf and process the result */
206                 theDigest.doFinal(theHash, 0);
207                 theStore.addElement(theHash);
208                 theDigest.setNodePosition(theDigest.getNodeOffset() + 1, 0);
209                 theProcessed = 0;
210             }
211 
212             /* Process next block */
213             final int myDataLen = Math.min(pLen - myProcessed, blkSize);
214             theDigest.update(pIn, pInOffSet + myProcessed, myDataLen);
215             theProcessed += myDataLen;
216             myProcessed += myDataLen;
217         }
218     }
219 
220     /**
221      * Initialise.
222      *
223      * @param pParams the parameters.
224      */
225     public void init(final GordianBlake2Parameters pParams) {
226         /* Check that we have a fanOut != 1 && MaxDepth >= 1*/
227         if (pParams.getTreeFanOut() == 1) {
228             throw new IllegalArgumentException("FanOut cannot be 1");
229         }
230         if (pParams.getTreeMaxDepth() < 2) {
231             throw new IllegalArgumentException("MaxDepth must be greater than 1");
232         }
233 
234         /* Pass selective parameters to the underlying instance */
235         theDigest.setKey(pParams.getKey());
236         theDigest.setSalt(pParams.getSalt());
237         theDigest.setPersonalisation(pParams.getPersonalisation());
238         theDigest.setTreeConfig(pParams.getTreeFanOut(), pParams.getTreeMaxDepth(), pParams.getTreeLeafLen());
239         theDigest.setNodePosition(0, 0);
240 
241         /* Reset processed and init the store */
242         theProcessed = 0;
243         theStore.init(pParams);
244     }
245 
246     /**
247      * Update leaf.
248      *
249      * @param pIndex    the index of the leaf
250      * @param pInput    the input buffer
251      * @param pInOffSet the starting offset the the input buffer
252      */
253     public void updateLeaf(final int pIndex,
254                            final byte[] pInput,
255                            final int pInOffSet) {
256         /* Full leafLen */
257         updateLeaf(pIndex, pInput, pInOffSet, getLeafLen());
258     }
259 
260     /**
261      * Update leaf.
262      *
263      * @param pIndex    the index of the leaf
264      * @param pInput    the input buffer
265      * @param pInOffSet the starting offset the the input buffer
266      * @param pLen      the length of data
267      */
268     public void updateLeaf(final int pIndex,
269                            final byte[] pInput,
270                            final int pInOffSet,
271                            final int pLen) {
272         /* Check index validity */
273         final boolean bLast = theStore.checkLeafIndex(pIndex);
274 
275         /* Validate the leaf length */
276         final int myLeafLen = getLeafLen();
277         if (pLen < 0 || pLen > myLeafLen) {
278             throw new DataLengthException("Invalid length");
279         }
280 
281         /* Any leaf that is not the last must be leafLen in length */
282         if (!bLast && pLen != myLeafLen) {
283             throw new DataLengthException("All but the last leaf must have byteLength " + myLeafLen);
284         }
285 
286         /* Make sure that the buffer is valid */
287         if (pLen + pInOffSet > pInput.length) {
288             throw new DataLengthException("Invalid input buffer");
289         }
290 
291         /* Initialise the node and note if last node */
292         theDigest.setNodePosition(pIndex, 0);
293         if (bLast) {
294             theDigest.setLastNode();
295         }
296 
297         /* Recalculate the digest */
298         theDigest.update(pInput, pInOffSet, pLen);
299         theDigest.doFinal(theHash, 0);
300 
301         /* Replace the hash */
302         theStore.replaceElement(pIndex, theHash);
303     }
304 
305     /**
306      * The Blake tree.
307      */
308     private static class GordianBlake2TreeStore {
309         /**
310          * The underlying Blake2 instance.
311          */
312         private final GordianBlake2Base theDigest;
313 
314         /**
315          * The Hash Result.
316          */
317         private final byte[] theResult;
318 
319         /**
320          * The Array of Hashes.
321          */
322         private final SimpleVector theHashes;
323 
324         /**
325          * Has the tree been built?.
326          */
327         private boolean treeBuilt;
328 
329         /**
330          * Constructor.
331          *
332          * @param pDigest the underlying digest.
333          */
334         GordianBlake2TreeStore(final GordianBlake2Base pDigest) {
335             theDigest = (GordianBlake2Base) pDigest.copy();
336             theResult = new byte[theDigest.getDigestSize()];
337             theHashes = new SimpleVector();
338         }
339 
340         /**
341          * Initialise.
342          *
343          * @param pParams the parameters.
344          */
345         public void init(final GordianBlake2Parameters pParams) {
346             /* Pass selective parameters to the underlying instance */
347             theDigest.setSalt(pParams.getSalt());
348             theDigest.setPersonalisation(pParams.getPersonalisation());
349             theDigest.setTreeConfig(pParams.getTreeFanOut(), pParams.getTreeMaxDepth(), pParams.getTreeLeafLen());
350             reset();
351         }
352 
353         /**
354          * Has the tree been built?
355          *
356          * @return true/false
357          */
358         boolean treeBuilt() {
359             return treeBuilt;
360         }
361 
362         /**
363          * Reset the store.
364          */
365         void reset() {
366             theDigest.setNodePosition(0, 1);
367             theHashes.clear();
368             treeBuilt = false;
369         }
370 
371         /**
372          * Add intermediate node.
373          *
374          * @param pHash the intermediate hash
375          */
376         void addElement(final byte[] pHash) {
377             /* Access the base level */
378             if (theHashes.isEmpty()) {
379                 theHashes.addElement(new SimpleVector());
380             }
381             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
382 
383             /* Add the element to the vector */
384             myLevel.addElement(Arrays.clone(pHash));
385         }
386 
387         /**
388          * Obtain the tree result.
389          *
390          * @param pOut       the output buffer
391          * @param pOutOffset the offset into the output buffer
392          * @return the number of bytes returned
393          */
394         int obtainResult(final byte[] pOut,
395                          final int pOutOffset) {
396             /* Check parameters */
397             if (pOut.length < pOutOffset + theResult.length) {
398                 throw new OutputLengthException("Insufficient output buffer");
399             }
400             if (!treeBuilt) {
401                 throw new IllegalStateException("tree has not been built");
402             }
403 
404             /* Access the final level */
405             final SimpleVector myLevel = (SimpleVector) theHashes.lastElement();
406             final byte[] myResult = (byte[]) myLevel.firstElement();
407 
408             /* Return the final hash */
409             System.arraycopy(myResult, 0, pOut, pOutOffset, myResult.length);
410             return myResult.length;
411         }
412 
413         /**
414          * Calculate tree result.
415          *
416          * @param pOut       the output buffer
417          * @param pOutOffset the offset into the output buffer
418          * @return the number of bytes returned
419          */
420         int calculateTree(final byte[] pOut,
421                           final int pOutOffset) {
422             /* Check parameters */
423             if (pOut.length < pOutOffset + theResult.length) {
424                 throw new OutputLengthException("Insufficient output buffer");
425             }
426             if (treeBuilt) {
427                 throw new IllegalStateException("tree already built");
428             }
429 
430             /* Access the only level */
431             SimpleVector myLevel = (SimpleVector) theHashes.lastElement();
432 
433             /* While we have elements that must be reduced */
434             while (myLevel.size() > 1) {
435                 /* Calculate the next set of hashes */
436                 myLevel = calculateNextLevel(myLevel);
437                 theHashes.addElement(myLevel);
438             }
439 
440             /* Note that the tree has been built */
441             treeBuilt = true;
442 
443             /* Return the final hash */
444             return obtainResult(pOut, pOutOffset);
445         }
446 
447         /**
448          * Calculate next level.
449          *
450          * @param pInput the current set of hashes
451          * @return the next level
452          */
453         private SimpleVector calculateNextLevel(final SimpleVector pInput) {
454             /* Set the depth of the tree */
455             final int myCurDepth = theHashes.size();
456             final int myMaxDepth = theDigest.getMaxDepth();
457             final int myFanOut = theDigest.getFanOut();
458             theDigest.setNodePosition(0, myCurDepth);
459 
460             /* Create the new level */
461             final SimpleVector myResults = new SimpleVector();
462 
463             /* Determine whether we are calculating the root node */
464             final boolean lastStage = myFanOut == 0
465                     || pInput.size() <= myFanOut
466                     || myCurDepth == myMaxDepth - 1;
467 
468             /* Loop through all the elements */
469             int myCount = 0;
470             int myOffSet = 0;
471             final Enumeration<?> myEnumeration = pInput.elements();
472             while (myEnumeration.hasMoreElements()) {
473                 /* If we need to move to the next node  */
474                 if (!lastStage && myCount == myFanOut) {
475                     /* Calculate node and add to level */
476                     theDigest.doFinal(theResult, 0);
477                     myResults.addElement(Arrays.clone(theResult));
478 
479                     /* Switch to next node */
480                     theDigest.setNodePosition(++myOffSet, myCurDepth);
481                     myCount = 0;
482                 }
483 
484                 /* Fold hash into current node */
485                 final byte[] myHash = (byte[]) myEnumeration.nextElement();
486                 theDigest.update(myHash, 0, myHash.length);
487                 myCount++;
488             }
489 
490             /* Calculate final node at this level */
491             theDigest.setLastNode();
492             theDigest.doFinal(theResult, 0);
493             myResults.addElement(Arrays.clone(theResult));
494 
495             /* Return the results */
496             return myResults;
497         }
498 
499         /**
500          * Check the leaf index.
501          *
502          * @param pIndex the index of the element
503          * @return is this the last element in the tree? true/false
504          */
505         boolean checkLeafIndex(final int pIndex) {
506             /* Cannot replace leaf if not built */
507             if (!treeBuilt) {
508                 throw new IllegalStateException("Tree has not been built");
509             }
510 
511             /* Check that the index is valid */
512             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
513             if (pIndex < 0 || pIndex >= myLevel.size()) {
514                 throw new IllegalArgumentException("Invalid index");
515             }
516 
517             /* Return whether this is the last index */
518             return pIndex == myLevel.size() - 1;
519         }
520 
521         /**
522          * Replace the hash for a leaf node.
523          *
524          * @param pIndex the index of the element
525          * @param pHash  the new hashValue
526          */
527         void replaceElement(final int pIndex,
528                             final byte[] pHash) {
529             /* Check that the index is correct */
530             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
531             if (pIndex < 0 || pIndex >= myLevel.size()) {
532                 throw new IllegalArgumentException("Invalid index");
533             }
534 
535             /* Replace the element */
536             myLevel.setElementAt(Arrays.clone(pHash), pIndex);
537 
538             /* Loop through the levels */
539             int myIndex = pIndex;
540             for (int i = 1; i < theHashes.size(); i++) {
541                 /* Recalculate the parent node */
542                 myIndex = recalculateParent(i, myIndex);
543             }
544         }
545 
546         /**
547          * Recalculate Node.
548          *
549          * @param pLevel the tree level of the node
550          * @param pIndex of the node
551          * @return the parent index
552          */
553         private int recalculateParent(final int pLevel,
554                                       final int pIndex) {
555             /* Make sure that the level is reasonable */
556             if (pLevel < 1 || pLevel >= theHashes.size()) {
557                 throw new IllegalArgumentException("Invalid level");
558             }
559 
560             /* Access the Vector for the parent and input levels */
561             final SimpleVector myInput = (SimpleVector) theHashes.elementAt(pLevel - 1);
562             final SimpleVector myLevel = (SimpleVector) theHashes.elementAt(pLevel);
563 
564             /* Access treeConfig */
565             final int myFanOut = theDigest.getFanOut();
566             final int myMaxDepth = theDigest.getMaxDepth();
567 
568             /* Determine whether we are calculating the root node */
569             final boolean lastStage = myFanOut == 0
570                     || myInput.size() <= myFanOut
571                     || pLevel == myMaxDepth - 1;
572 
573             /* Calculate bounds */
574             final int myParentIndex = lastStage
575                     ? 0
576                     : pIndex / myFanOut;
577             final int myIndex = myParentIndex * myFanOut;
578             final int myNumHashes = myInput.size();
579             final int myMaxHash = lastStage ? myNumHashes : Math.min(myFanOut, myNumHashes - myIndex);
580 
581             /* Initialise the digest */
582             theDigest.setNodePosition(myParentIndex, pLevel);
583 
584             /* Loop through the input hashes */
585             for (int i = 0; i < myMaxHash; i++) {
586                 /* Fold into new hash */
587                 final byte[] myHash = (byte[]) myInput.elementAt(i + myIndex);
588                 theDigest.update(myHash, 0, myHash.length);
589             }
590 
591             /* Note if we are the last of the level */
592             if (myIndex + myMaxHash == myNumHashes) {
593                 theDigest.setLastNode();
594             }
595 
596             /* Calculate new digest and replace it */
597             theDigest.doFinal(theResult, 0);
598             myLevel.setElementAt(Arrays.clone(theResult), myParentIndex);
599             return myParentIndex;
600         }
601     }
602 
603     /**
604      * Simple Vector class.
605      * <p>This is a cut down version of the java Vector class to avoid use of synchronised.
606      */
607     private static class SimpleVector {
608         /**
609          * The initial capacity.
610          */
611         private static final int INITCAPACITY = 8;
612 
613         /**
614          * The array buffer holding elements.
615          */
616         private Object[] elementData;
617 
618         /**
619          * The number of valid components in this {@code SimpleVector} object.
620          */
621         private int elementCount;
622 
623         /**
624          * Constructor.
625          */
626         SimpleVector() {
627             elementData = new Object[INITCAPACITY];
628         }
629 
630         /**
631          * Returns the number of components in this vector.
632          *
633          * @return the vector size
634          */
635         int size() {
636             return elementCount;
637         }
638 
639         /**
640          * Tests if this vector has no components.
641          *
642          * @return true/false
643          */
644         boolean isEmpty() {
645             return elementCount == 0;
646         }
647 
648         /**
649          * Returns the first component of the vector.
650          *
651          * @return the first component of the vector
652          * @throws NoSuchElementException if this vector is empty
653          */
654         Object firstElement() {
655             if (elementCount == 0) {
656                 throw new NoSuchElementException();
657             }
658             return elementData[0];
659         }
660 
661         /**
662          * Returns the last component of the vector.
663          *
664          * @return the last component of the vector, i.e., the component at index
665          * <code>size()&nbsp;-&nbsp;1</code>.
666          * @throws NoSuchElementException if this vector is empty
667          */
668         Object lastElement() {
669             if (elementCount == 0) {
670                 throw new NoSuchElementException();
671             }
672             return elementData[elementCount - 1];
673         }
674 
675         /**
676          * Returns the component at the specified index.
677          *
678          * @param index an index into this vector
679          * @return the component at the specified index
680          * @throws ArrayIndexOutOfBoundsException if the index is out of range
681          *                                        ({@code index < 0 || index >= size()})
682          */
683         Object elementAt(final int index) {
684             if (index >= elementCount) {
685                 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
686             }
687 
688             return elementData[index];
689         }
690 
691         /**
692          * Sets the component at the specified {@code index} of this
693          * vector to be the specified object. The previous component at that
694          * position is discarded.
695          *
696          * <p>The index must be a value greater than or equal to {@code 0}
697          * and less than the current size of the vector.
698          *
699          * @param obj   what the component is to be set to
700          * @param index the specified index
701          * @throws ArrayIndexOutOfBoundsException if the index is out of range
702          *                                        ({@code index < 0 || index >= size()})
703          */
704         void setElementAt(final Object obj,
705                           final int index) {
706             if (index >= elementCount) {
707                 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
708             }
709             elementData[index] = obj;
710         }
711 
712         /**
713          * Adds the specified component to the end of this vector,
714          * increasing its size by one. The capacity of this vector is
715          * increased if its size becomes greater than its capacity.
716          *
717          * @param obj the component to be added
718          */
719         void addElement(final Object obj) {
720             if (elementCount == elementData.length) {
721                 final Object[] newData = new Object[elementData.length << 1];
722                 System.arraycopy(elementData, 0, newData, 0, elementCount);
723                 elementData = newData;
724             }
725             elementData[elementCount++] = obj;
726         }
727 
728         /**
729          * Removes all of the elements from this Vector.  The Vector will
730          * be empty after this call returns (unless it throws an exception).
731          */
732         void clear() {
733             for (int i = 0; i < elementCount; i++) {
734                 elementData[i] = null;
735             }
736             elementCount = 0;
737         }
738 
739         /**
740          * Returns an enumeration of the components of this vector.
741          *
742          * @return the enumeration
743          */
744         Enumeration<Object> elements() {
745             return new Enumeration<>() {
746                 private int count;
747 
748                 public boolean hasMoreElements() {
749                     return count < elementCount;
750                 }
751 
752                 public Object nextElement() {
753                     if (count < elementCount) {
754                         return elementData[count++];
755                     }
756                     throw new NoSuchElementException("Vector Enumeration");
757                 }
758             };
759         }
760     }
761 }