View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 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  
18  package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
19  
20  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters;
21  import org.bouncycastle.crypto.OutputLengthException;
22  import org.bouncycastle.crypto.engines.ThreefishEngine;
23  import org.bouncycastle.crypto.macs.SkeinMac;
24  import org.bouncycastle.crypto.params.SkeinParameters;
25  import org.bouncycastle.util.Arrays;
26  import org.bouncycastle.util.Integers;
27  import org.bouncycastle.util.Memoable;
28  import org.bouncycastle.util.Pack;
29  
30  import java.util.Enumeration;
31  import java.util.HashMap;
32  import java.util.Hashtable;
33  import java.util.Map;
34  import java.util.Vector;
35  
36  /**
37   * Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block
38   * sizes, based on the {@link ThreefishEngine Threefish} tweakable block cipher.
39   * <p>
40   * This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3
41   * competition in October 2010.
42   * <p>
43   * Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir
44   * Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker.
45   * <p>
46   * This implementation is the basis for SkeinDigest and {@link SkeinMac}, implementing the
47   * parameter based configuration system that allows Skein to be adapted to multiple applications. <br>
48   * Initialising the engine with {@link GordianSkeinParameters} allows standard and arbitrary parameters to
49   * be applied during the Skein hash function.
50   * <p>
51   * Implemented:
52   * <ul>
53   * <li>256, 512 and 1024 bit internal states.</li>
54   * <li>Full 96 bit input length.</li>
55   * <li>Parameters defined in the Skein specification, and arbitrary other pre and post message
56   * parameters.</li>
57   * <li>Arbitrary output size in 1 byte intervals.</li>
58   * </ul>
59   * <p>
60   * Not implemented:
61   * <ul>
62   * <li>Sub-byte length input (bit padding).</li>
63   * <li>Tree hashing.</li>
64   * </ul>
65   *
66   * @see GordianSkeinParameters
67   */
68  @SuppressWarnings("checkstyle:MagicNumber")
69  public class GordianSkeinBase
70          implements Memoable {
71      /**
72       * 256 bit block size - Skein 256.
73       */
74      public static final int SKEIN_256 = ThreefishEngine.BLOCKSIZE_256;
75      /**
76       * 512 bit block size - Skein 512.
77       */
78      public static final int SKEIN_512 = ThreefishEngine.BLOCKSIZE_512;
79  
80      /**
81       * 1024 bit block size - Skein 1024.
82       */
83      public static final int SKEIN_1024 = ThreefishEngine.BLOCKSIZE_1024;
84  
85      /**
86       * Configuration. Minimal at present, but more complex when tree hashing is implemented
87       */
88      static class Configuration {
89          /**
90           * Configuration bytes.
91           */
92          private byte[] bytes = new byte[32];
93  
94          /**
95           * Constructor.
96           *
97           * @param outputSizeBits the output size in bits
98           */
99          Configuration(final long outputSizeBits) {
100             // 0..3 = ASCII SHA3
101             bytes[0] = (byte) 'S';
102             bytes[1] = (byte) 'H';
103             bytes[2] = (byte) 'A';
104             bytes[3] = (byte) '3';
105 
106             // 4..5 = version number in LSB order
107             bytes[4] = 1;
108             bytes[5] = 0;
109 
110             // 8..15 = output length
111             Pack.longToLittleEndian(outputSizeBits, bytes, 8);
112         }
113 
114         /**
115          * Obtain bytes.
116          *
117          * @return the bytes.
118          */
119         public byte[] getBytes() {
120             return bytes;
121         }
122     }
123 
124     /**
125      * Parameter class.
126      */
127     public static class Parameter {
128         /**
129          * The type.
130          */
131         private final int type;
132 
133         /**
134          * The value.
135          */
136         private final byte[] value;
137 
138         /**
139          * Constructor.
140          *
141          * @param pType  the type
142          * @param pValue the value
143          */
144         public Parameter(final int pType, final byte[] pValue) {
145             this.type = pType;
146             this.value = pValue;
147         }
148 
149         /**
150          * Obtain the type.
151          *
152          * @return the type
153          */
154         public int getType() {
155             return type;
156         }
157 
158         /**
159          * Obtain the value.
160          *
161          * @return the value
162          */
163         public byte[] getValue() {
164             return value;
165         }
166     }
167 
168     /**
169      * The parameter type for the Skein key.
170      */
171     private static final int PARAM_TYPE_KEY = 0;
172 
173     /**
174      * The parameter type for the Skein configuration block.
175      */
176     private static final int PARAM_TYPE_CONFIG = 4;
177 
178     /**
179      * The parameter type for the message.
180      */
181     private static final int PARAM_TYPE_MESSAGE = 48;
182 
183     /**
184      * The parameter type for the output transformation.
185      */
186     private static final int PARAM_TYPE_OUTPUT = 63;
187 
188     /**
189      * Precalculated UBI(CFG) states for common state/output combinations without key or other
190      * pre-message params.
191      */
192     private static final Map<Integer, long[]> INITIAL_STATES = new HashMap<>();
193 
194     static {
195         // From Appendix C of the Skein 1.3 NIST submission
196         final long[] skein256o128 = {
197                 0xe1111906964d7260L,
198                 0x883daaa77c8d811cL,
199                 0x10080df491960f7aL,
200                 0xccf7dde5b45bc1c2L
201         };
202         initialState(SKEIN_256, 128, skein256o128);
203 
204         final long[] skein256o160 = {
205                 0x1420231472825e98L,
206                 0x2ac4e9a25a77e590L,
207                 0xd47a58568838d63eL,
208                 0x2dd2e4968586ab7dL
209         };
210         initialState(SKEIN_256, 160, skein256o160);
211 
212         final long[] skein256o224 = {
213                 0xc6098a8c9ae5ea0bL,
214                 0x876d568608c5191cL,
215                 0x99cb88d7d7f53884L,
216                 0x384bddb1aeddb5deL
217         };
218         initialState(SKEIN_256, 224, skein256o224);
219 
220         final long[] skein256o256 = {
221                 0xfc9da860d048b449L,
222                 0x2fca66479fa7d833L,
223                 0xb33bc3896656840fL,
224                 0x6a54e920fde8da69L
225         };
226         initialState(SKEIN_256, 256, skein256o256);
227 
228         final long[] skein512o128 = {
229                 0xa8bc7bf36fbf9f52L,
230                 0x1e9872cebd1af0aaL,
231                 0x309b1790b32190d3L,
232                 0xbcfbb8543f94805cL,
233                 0x0da61bcd6e31b11bL,
234                 0x1a18ebead46a32e3L,
235                 0xa2cc5b18ce84aa82L,
236                 0x6982ab289d46982dL
237         };
238         initialState(SKEIN_512, 128, skein512o128);
239 
240         final long[] skein512o160 = {
241                 0x28b81a2ae013bd91L,
242                 0xc2f11668b5bdf78fL,
243                 0x1760d8f3f6a56f12L,
244                 0x4fb747588239904fL,
245                 0x21ede07f7eaf5056L,
246                 0xd908922e63ed70b8L,
247                 0xb8ec76ffeccb52faL,
248                 0x01a47bb8a3f27a6eL
249         };
250         initialState(SKEIN_512, 160, skein512o160);
251 
252         final long[] skein512o224 = {
253                 0xccd0616248677224L,
254                 0xcba65cf3a92339efL,
255                 0x8ccd69d652ff4b64L,
256                 0x398aed7b3ab890b4L,
257                 0x0f59d1b1457d2bd0L,
258                 0x6776fe6575d4eb3dL,
259                 0x99fbc70e997413e9L,
260                 0x9e2cfccfe1c41ef7L
261         };
262         initialState(SKEIN_512, 224, skein512o224);
263 
264         final long[] skein512o384 = {
265                 0xa3f6c6bf3a75ef5fL,
266                 0xb0fef9ccfd84faa4L,
267                 0x9d77dd663d770cfeL,
268                 0xd798cbf3b468fddaL,
269                 0x1bc4a6668a0e4465L,
270                 0x7ed7d434e5807407L,
271                 0x548fc1acd4ec44d6L,
272                 0x266e17546aa18ff8L
273         };
274         initialState(SKEIN_512, 384, skein512o384);
275 
276         final long[] skein512o512 = {
277                 0x4903adff749c51ceL,
278                 0x0d95de399746df03L,
279                 0x8fd1934127c79bceL,
280                 0x9a255629ff352cb1L,
281                 0x5db62599df6ca7b0L,
282                 0xeabe394ca9d5c3f4L,
283                 0x991112c71a75b523L,
284                 0xae18a40b660fcc33L
285         };
286         initialState(SKEIN_512, 512, skein512o512);
287     }
288 
289     private static void initialState(final int blockSize, final int outputSize, final long[] state) {
290         INITIAL_STATES.put(variantIdentifier(blockSize / 8, outputSize / 8), state);
291     }
292 
293     private static Integer variantIdentifier(final int blockSizeBytes, final int outputSizeBytes) {
294         return Integers.valueOf((outputSizeBytes << 16) | blockSizeBytes);
295     }
296 
297     /**
298      * Ubi Tweak class.
299      */
300     private static class UbiTweak {
301         /**
302          * Point at which position might overflow long, so switch to add with carry logic.
303          */
304         private static final long LOW_RANGE = Long.MAX_VALUE - Integer.MAX_VALUE;
305 
306         /**
307          * Bit 127 = final.
308          */
309         private static final long T1_FINAL = 1L << 63;
310 
311         /**
312          * Bit 126 = first.
313          */
314         private static final long T1_FIRST = 1L << 62;
315 
316         /**
317          * UBI uses a 128 bit tweak.
318          */
319         private long[] tweak = new long[2];
320 
321         /**
322          * Whether 64 bit position exceeded.
323          */
324         private boolean extendedPosition;
325 
326         /**
327          * Constructor.
328          */
329         UbiTweak() {
330             reset();
331         }
332 
333         /**
334          * Reset according to source.
335          *
336          * @param pTweak the source
337          */
338         public void reset(final UbiTweak pTweak) {
339             this.tweak = Arrays.clone(pTweak.tweak, this.tweak);
340             this.extendedPosition = pTweak.extendedPosition;
341         }
342 
343         /**
344          * Reset.
345          */
346         public void reset() {
347             tweak[0] = 0;
348             tweak[1] = 0;
349             extendedPosition = false;
350             setFirst(true);
351         }
352 
353         public void setType(final int type) {
354             // Bits 120..125 = type
355             tweak[1] = (tweak[1] & 0xFFFFFFC000000000L) | ((type & 0x3FL) << 56);
356         }
357 
358         public int getType() {
359             return (int) ((tweak[1] >>> 56) & 0x3FL);
360         }
361 
362         public void setTreeLocation(final int level, final int hiOffset, final long loOffset) {
363             // Bits 112..119 = level
364             tweak[1] = (tweak[1] & 0xFF00FFC000000000L) | ((level & 0xFFL) << 48);
365 
366             /* Calculate high and lo parts of offset */
367             tweak[0] = loOffset;
368             tweak[1] |= hiOffset;
369 
370             /* Determine whether we have an extended position */
371             extendedPosition = loOffset < 0 || loOffset > LOW_RANGE || hiOffset != 0;
372         }
373 
374         public int getLevel() {
375             return (int) ((tweak[1] >>> 48) & 0xFFL);
376         }
377 
378         public void setFirst(final boolean first) {
379             if (first) {
380                 tweak[1] |= T1_FIRST;
381             } else {
382                 tweak[1] &= ~T1_FIRST;
383             }
384         }
385 
386         public boolean isFirst() {
387             return ((tweak[1] & T1_FIRST) != 0);
388         }
389 
390         public void setFinal(final boolean last) {
391             if (last) {
392                 tweak[1] |= T1_FINAL;
393             } else {
394                 tweak[1] &= ~T1_FINAL;
395             }
396         }
397 
398         public boolean isFinal() {
399             return ((tweak[1] & T1_FINAL) != 0);
400         }
401 
402         /**
403          * Advances the position in the tweak by the specified value.
404          *
405          * @param advance the advance count
406          */
407         public void advancePosition(final int advance) {
408             // Bits 0..95 = position
409             if (extendedPosition) {
410                 final long[] parts = new long[3];
411                 parts[0] = tweak[0] & 0xFFFFFFFFL;
412                 parts[1] = (tweak[0] >>> 32) & 0xFFFFFFFFL;
413                 parts[2] = tweak[1] & 0xFFFFFFFFL;
414 
415                 long carry = advance;
416                 for (int i = 0; i < parts.length; i++) {
417                     carry += parts[i];
418                     parts[i] = carry;
419                     carry >>>= 32;
420                 }
421                 tweak[0] = ((parts[1] & 0xFFFFFFFFL) << 32) | (parts[0] & 0xFFFFFFFFL);
422                 tweak[1] = (tweak[1] & 0xFFFFFFFF00000000L) | (parts[2] & 0xFFFFFFFFL);
423             } else {
424                 long position = tweak[0];
425                 position += advance;
426                 tweak[0] = position;
427                 if (position > LOW_RANGE) {
428                     extendedPosition = true;
429                 }
430             }
431         }
432 
433         public long[] getWords() {
434             return tweak;
435         }
436 
437         public String toString() {
438             return getType() + " first: " + isFirst() + ", final: " + isFinal();
439         }
440 
441     }
442 
443     /**
444      * The Unique Block Iteration chaining mode.
445      */
446     private class UBI {
447         /**
448          * The tweak.
449          */
450         private final UbiTweak tweak = new UbiTweak();
451 
452         /**
453          * Buffer for the current block of message data.
454          */
455         private byte[] currentBlock;
456 
457         /**
458          * Offset into the current message block.
459          */
460         private int currentOffset;
461 
462         /**
463          * Buffer for message words for feedback into encrypted block.
464          */
465         private long[] message;
466 
467         UBI(final int blockSize) {
468             currentBlock = new byte[blockSize];
469             message = new long[currentBlock.length / 8];
470         }
471 
472         public void reset(final UBI pUbi) {
473             currentBlock = Arrays.clone(pUbi.currentBlock, currentBlock);
474             currentOffset = pUbi.currentOffset;
475             message = Arrays.clone(pUbi.message, this.message);
476             tweak.reset(pUbi.tweak);
477         }
478 
479         public void reset(final int type) {
480             tweak.reset();
481             tweak.setType(type);
482             currentOffset = 0;
483         }
484 
485         public void setTreeLocation(final int level, final long offSet, final int pShift) {
486             int shift = getBlockSize() << 3;
487             shift += pShift; //level == 0 ? leafLen : fanOut;
488             final int hiOffset = (int) offSet >>> 64 - shift;
489             final long loOffset = offSet << shift;
490             tweak.setTreeLocation(level, hiOffset, loOffset);
491         }
492 
493         public void update(final byte[] value, final int offset, final int len, final long[] output) {
494             /*
495              * Buffer complete blocks for the underlying Threefish cipher, only flushing when there
496              * are subsequent bytes (last block must be processed in doFinal() with final=true set).
497              */
498             int copied = 0;
499             while (len > copied) {
500                 if (currentOffset == currentBlock.length) {
501                     processBlock(output);
502                     tweak.setFirst(false);
503                     currentOffset = 0;
504                 }
505 
506                 final int toCopy = Math.min((len - copied), currentBlock.length - currentOffset);
507                 System.arraycopy(value, offset + copied, currentBlock, currentOffset, toCopy);
508                 copied += toCopy;
509                 currentOffset += toCopy;
510                 tweak.advancePosition(toCopy);
511             }
512         }
513 
514         private void processBlock(final long[] output) {
515             threefish.init(true, chain, tweak.getWords());
516             for (int i = 0; i < message.length; i++) {
517                 message[i] = Pack.littleEndianToLong(currentBlock, i * 8);
518             }
519 
520             threefish.processBlock(message, output);
521 
522             for (int i = 0; i < output.length; i++) {
523                 output[i] ^= message[i];
524             }
525         }
526 
527         public void doFinal(final long[] output) {
528             // Pad remainder of current block with zeroes
529             for (int i = currentOffset; i < currentBlock.length; i++) {
530                 currentBlock[i] = 0;
531             }
532 
533             tweak.setFinal(true);
534             processBlock(output);
535         }
536     }
537 
538     /**
539      * Underlying Threefish tweakable block cipher.
540      */
541     private final ThreefishEngine threefish;
542 
543     /**
544      * Size of the digest output, in bytes.
545      */
546     private final int outputSizeBytes;
547 
548     /**
549      * The current chaining/state value.
550      */
551     private long[] chain;
552 
553     /**
554      * The initial state value.
555      */
556     private long[] initialState;
557 
558     /**
559      * The (optional) key parameter.
560      */
561     private byte[] key;
562 
563     /**
564      * Parameters to apply prior to the message.
565      */
566     private Parameter[] preMessageParameters;
567 
568     /**
569      * Parameters to apply after the message, but prior to output.
570      */
571     private Parameter[] postMessageParameters;
572 
573     /**
574      * The current UBI operation.
575      */
576     private final UBI ubi;
577 
578     /**
579      * Buffer for single byte update method.
580      */
581     private final byte[] singleByte = new byte[1];
582 
583     /**
584      * The explicit configuration.
585      */
586     private Configuration theConfig;
587 
588     /**
589      * Constructs a Skein engine.
590      *
591      * @param blockSizeBits  the internal state size in bits - one of {@link #SKEIN_256}, {@link #SKEIN_512} or
592      *                       {@link #SKEIN_1024}.
593      * @param outputSizeBits the output/digest size to produce in bits, which must be an integral number of
594      *                       bytes.
595      */
596     public GordianSkeinBase(final int blockSizeBits, final int outputSizeBits) {
597         if (outputSizeBits % 8 != 0) {
598             throw new IllegalArgumentException("Output size must be a multiple of 8 bits. :" + outputSizeBits);
599         }
600         this.outputSizeBytes = outputSizeBits / Byte.SIZE;
601 
602         this.threefish = new ThreefishEngine(blockSizeBits);
603         this.ubi = new UBI(threefish.getBlockSize());
604     }
605 
606     /**
607      * Creates a SkeinEngine as an exact copy of an existing instance.
608      *
609      * @param engine the base engine
610      */
611     public GordianSkeinBase(final GordianSkeinBase engine) {
612         this(engine.getBlockSize() * 8, engine.getOutputSize() * 8);
613         copyIn(engine);
614     }
615 
616     private void copyIn(final GordianSkeinBase engine) {
617         this.ubi.reset(engine.ubi);
618         this.chain = Arrays.clone(engine.chain, this.chain);
619         this.initialState = Arrays.clone(engine.initialState, this.initialState);
620         this.key = Arrays.clone(engine.key, this.key);
621         this.preMessageParameters = clone(engine.preMessageParameters, this.preMessageParameters);
622         this.postMessageParameters = clone(engine.postMessageParameters, this.postMessageParameters);
623     }
624 
625     private static Parameter[] clone(final Parameter[] data, final Parameter[] existing) {
626         if (data == null) {
627             return null;
628         }
629         Parameter[] myExisting = existing;
630         if ((myExisting == null) || (myExisting.length != data.length)) {
631             myExisting = new Parameter[data.length];
632         }
633         System.arraycopy(data, 0, myExisting, 0, myExisting.length);
634         return existing;
635     }
636 
637     @Override
638     public Memoable copy() {
639         return new GordianSkeinBase(this);
640     }
641 
642     @Override
643     public void reset(final Memoable other) {
644         final GordianSkeinBase s = (GordianSkeinBase) other;
645         if ((getBlockSize() != s.getBlockSize()) || (outputSizeBytes != s.outputSizeBytes)) {
646             throw new IllegalArgumentException("Incompatible parameters in provided SkeinEngine.");
647         }
648         copyIn(s);
649     }
650 
651     /**
652      * Get output size.
653      *
654      * @return the output size
655      */
656     public int getOutputSize() {
657         return outputSizeBytes;
658     }
659 
660     /**
661      * Obtain the block size.
662      *
663      * @return the block size
664      */
665     public int getBlockSize() {
666         return threefish.getBlockSize();
667     }
668 
669     /**
670      * Set the extended configuration.
671      *
672      * @param pConfig the extended configuration
673      */
674     void setConfiguration(final Configuration pConfig) {
675         theConfig = pConfig;
676     }
677 
678     /**
679      * Initialises the Skein engine with the provided parameters. See {@link GordianSkeinParameters} for
680      * details on the parameterisation of the Skein hash function.
681      *
682      * @param params the parameters to apply to this engine, or <code>null</code> to use no parameters.
683      */
684     @SuppressWarnings("unchecked")
685     public void init(final GordianSkeinParameters params) {
686         this.chain = null;
687         this.key = null;
688         this.preMessageParameters = null;
689         this.postMessageParameters = null;
690 
691         if (params != null) {
692             final byte[] theKey = params.getKey();
693             if (theKey != null && theKey.length < 16) {
694                 throw new IllegalArgumentException("Skein key must be at least 128 bits.");
695             }
696             initParams(params.getParameters());
697         }
698         createInitialState();
699 
700         // Initialise message block
701         ubiInit(PARAM_TYPE_MESSAGE);
702     }
703 
704     private void initParams(final Hashtable<Integer, byte[]> parameters) {
705         final Enumeration<Integer> keys = parameters.keys();
706         final Vector<Parameter> pre = new Vector<>();
707         final Vector<Parameter> post = new Vector<>();
708 
709         while (keys.hasMoreElements()) {
710             final Integer type = keys.nextElement();
711             final byte[] value = parameters.get(type);
712 
713             if (type == PARAM_TYPE_KEY) {
714                 this.key = value;
715             } else if (type < PARAM_TYPE_MESSAGE) {
716                 pre.addElement(new Parameter(type, value));
717             } else {
718                 post.addElement(new Parameter(type, value));
719             }
720         }
721         preMessageParameters = new Parameter[pre.size()];
722         pre.copyInto(preMessageParameters);
723         sort(preMessageParameters);
724 
725         postMessageParameters = new Parameter[post.size()];
726         post.copyInto(postMessageParameters);
727         sort(postMessageParameters);
728     }
729 
730     private static void sort(final Parameter[] params) {
731         if (params == null) {
732             return;
733         }
734 
735         // Insertion sort, for Java 1.1 compatibility
736         for (int i = 1; i < params.length; i++) {
737             final Parameter param = params[i];
738             int hole = i;
739             while (hole > 0 && param.getType() < params[hole - 1].getType()) {
740                 params[hole] = params[hole - 1];
741                 hole = hole - 1;
742             }
743             params[hole] = param;
744         }
745     }
746 
747     /**
748      * Calculate the initial (pre message block) chaining state.
749      */
750     private void createInitialState() {
751         final boolean xtendedConfig = theConfig != null;
752         final long[] precalc = xtendedConfig ? null : INITIAL_STATES.get(variantIdentifier(getBlockSize(), getOutputSize()));
753         if ((key == null) && (precalc != null)) {
754             // Precalculated UBI(CFG)
755             chain = Arrays.clone(precalc);
756         } else {
757             // Blank initial state
758             chain = new long[getBlockSize() / 8];
759 
760             // Process key block
761             if (key != null) {
762                 ubiComplete(SkeinParameters.PARAM_TYPE_KEY, key);
763             }
764 
765             // Process configuration block
766             final Configuration myConfig = xtendedConfig ? theConfig : new Configuration(outputSizeBytes * 8L);
767             ubiComplete(PARAM_TYPE_CONFIG, myConfig.getBytes());
768         }
769 
770         // Process additional pre-message parameters
771         if (preMessageParameters != null) {
772             for (int i = 0; i < preMessageParameters.length; i++) {
773                 final Parameter param = preMessageParameters[i];
774                 ubiComplete(param.getType(), param.getValue());
775             }
776         }
777         initialState = Arrays.clone(chain);
778     }
779 
780     /**
781      * Reset the engine to the initial state (with the key and any pre-message parameters , ready to
782      * accept message input.
783      */
784     public void reset() {
785         System.arraycopy(initialState, 0, chain, 0, chain.length);
786 
787         ubiInit(PARAM_TYPE_MESSAGE);
788     }
789 
790     void initTreeNode(final int level, final long offset, final int shift) {
791         reset();
792         ubi.setTreeLocation(level, offset, shift);
793     }
794 
795     private void ubiComplete(final int type, final byte[] value) {
796         ubiInit(type);
797         this.ubi.update(value, 0, value.length, chain);
798         ubiFinal();
799     }
800 
801     private void ubiInit(final int type) {
802         this.ubi.reset(type);
803     }
804 
805     private void ubiFinal() {
806         ubi.doFinal(chain);
807     }
808 
809     private void checkInitialised() {
810         if (this.ubi == null) {
811             throw new IllegalArgumentException("Skein engine is not initialised.");
812         }
813     }
814 
815     /**
816      * Update the digest.
817      *
818      * @param in the byte to update with
819      */
820     public void update(final byte in) {
821         singleByte[0] = in;
822         update(singleByte, 0, 1);
823     }
824 
825     /**
826      * Update the digest.
827      *
828      * @param in    the input buffer
829      * @param inOff the input offset
830      * @param len   the input length
831      */
832     public void update(final byte[] in, final int inOff, final int len) {
833         checkInitialised();
834         ubi.update(in, inOff, len, chain);
835     }
836 
837     void calculateNode(final byte[] out, final int outOff) {
838         checkInitialised();
839         if (out.length < (outOff + outputSizeBytes)) {
840             throw new OutputLengthException("Output buffer is too short to hold output");
841         }
842 
843         // Finalise message block
844         ubiFinal();
845 
846         /* Output the state */
847         for (int i = 0; i < chain.length; i++) {
848             Pack.longToLittleEndian(chain[i], out, outOff + (i * 8));
849         }
850     }
851 
852     /**
853      * Finalise the digest.
854      *
855      * @param out    the output buffer
856      * @param outOff the output offset
857      * @return the number of bytes returned
858      */
859     public int doFinal(final byte[] out, final int outOff) {
860         checkInitialised();
861         if (out.length < (outOff + outputSizeBytes)) {
862             throw new OutputLengthException("Output buffer is too short to hold output");
863         }
864 
865         // Initiate output
866         initiateOutput();
867 
868         // Perform the output transform
869         final int blockSize = getBlockSize();
870         final int blocksRequired = ((outputSizeBytes + blockSize - 1) / blockSize);
871         for (int i = 0; i < blocksRequired; i++) {
872             final int toWrite = Math.min(blockSize, outputSizeBytes - (i * blockSize));
873             output(i, out, outOff + (i * blockSize), toWrite);
874         }
875 
876         reset();
877 
878         return outputSizeBytes;
879     }
880 
881     void postProcessMessage() {
882         // Process additional post-message parameters
883         if (postMessageParameters != null) {
884             for (int i = 0; i < postMessageParameters.length; i++) {
885                 final Parameter param = postMessageParameters[i];
886                 ubiComplete(param.getType(), param.getValue());
887             }
888         }
889     }
890 
891     void initiateOutput() {
892         // Finalise message block
893         ubiFinal();
894 
895         // Process additional post-message parameters
896         postProcessMessage();
897     }
898 
899     void restoreForOutput(final byte[] pState) {
900         /* Restore the state */
901         for (int i = 0; i < chain.length; i++) {
902             chain[i] = Pack.littleEndianToLong(pState, i * 8);
903         }
904 
905         // Initiate output
906         initiateOutput();
907     }
908 
909     void output(final long outputSequence, final byte[] out, final int outOff, final int outputBytes) {
910         final byte[] currentBytes = new byte[8];
911         Pack.longToLittleEndian(outputSequence, currentBytes, 0);
912 
913         // Output is a sequence of UBI invocations all of which use and preserve the pre-output
914         // state
915         final long[] outputWords = new long[chain.length];
916         ubiInit(PARAM_TYPE_OUTPUT);
917         this.ubi.update(currentBytes, 0, currentBytes.length, outputWords);
918         ubi.doFinal(outputWords);
919 
920         final int wordsRequired = ((outputBytes + 8 - 1) / 8);
921         for (int i = 0; i < wordsRequired; i++) {
922             final int toWrite = Math.min(8, outputBytes - (i * 8));
923             if (toWrite == 8) {
924                 Pack.longToLittleEndian(outputWords[i], out, outOff + (i * 8));
925             } else {
926                 Pack.longToLittleEndian(outputWords[i], currentBytes, 0);
927                 System.arraycopy(currentBytes, 0, out, outOff + (i * 8), toWrite);
928             }
929         }
930     }
931 }