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() - 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 }