package gnu.crypto.mac;

// ----------------------------------------------------------------------------
// $Id: UHash32.java,v 1.7 2005/10/06 04:24:16 rsdio Exp $
//
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// GNU Crypto is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING.  If not, write to the
//
//    Free Software Foundation Inc.,
//    51 Franklin Street, Fifth Floor,
//    Boston, MA 02110-1301
//    USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module.  An independent module is a module which is
// not derived from or based on this library.  If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so.  If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------

import gnu.crypto.cipher.IBlockCipher;
import gnu.crypto.prng.IRandom;
import gnu.crypto.prng.LimitReachedException;
import gnu.crypto.prng.UMacGenerator;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map;

/**
 * <p><i>UHASH</i> is a keyed hash function, which takes as input a string of
 * arbitrary length, and produces as output a string of fixed length (such as 8
 * bytes). The actual output length depends on the parameter UMAC-OUTPUT-LEN.</p>
 *
 * <p><i>UHASH</i> has been shown to be <i>epsilon-ASU</i> ("Almost Strongly
 * Universal"), where epsilon is a small (parameter-dependent) real number.
 * Informally, saying that a keyed hash function is <i>epsilon-ASU</i> means
 * that for any two distinct fixed input strings, the two outputs of the hash
 * function with a random key "look almost like a pair of random strings". The
 * number epsilon measures how non-random the output strings may be.</p>
 *
 * <i>UHASH</i> has been designed to be fast by exploiting several architectural
 * features of modern commodity processors. It was specifically designed for use
 * in <i>UMAC</i>. But <i>UHASH</i> is useful beyond that domain, and can be
 * easily adopted for other purposes.</p>
 *
 * <i>UHASH</i> does its work in three layers. First, a hash function called
 * <code>NH</code> is used to compress input messages into strings which are
 * typically many times smaller than the input message. Second, the compressed
 * message is hashed with an optimized <i>polynomial hash function</i> into a
 * fixed-length 16-byte string. Finally, the 16-byte string is hashed using an
 * <i>inner-product hash</i> into a string of length WORD-LEN bytes. These three
 * layers are repeated (with a modified key) until the outputs total
 * UMAC-OUTPUT-LEN bytes.</p>
 *
 * <p>References:</p>
 *
 * <ol>
 *    <li><a href="http://www.ietf.org/internet-drafts/draft-krovetz-umac-01.txt">
 *    UMAC</a>: Message Authentication Code using Universal Hashing.<br>
 *    T. Krovetz, J. Black, S. Halevi, A. Hevia, H. Krawczyk, and P. Rogaway.</li>
 * </ol>
 *
 * @version $Revision: 1.7 $
 */
public class UHash32 extends BaseMac {

   // Constants and variables
   // -------------------------------------------------------------------------

   // UMAC prime values
   private static final BigInteger PRIME_19 = BigInteger.valueOf(0x7FFFFL);
   private static final BigInteger PRIME_32 = BigInteger.valueOf(0xFFFFFFFBL);
   private static final BigInteger PRIME_36 = BigInteger.valueOf(0xFFFFFFFFBL);
   private static final BigInteger PRIME_64 =  new BigInteger(1,
         new byte[] {
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC5
         });
   private static final BigInteger PRIME_128 = new BigInteger(1,
         new byte[] {
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x61
         });

   static final BigInteger TWO = BigInteger.valueOf(2L);
   static final long BOUNDARY = TWO.shiftLeft(17).longValue();

   // 2**64 - 2**32
   static final BigInteger LOWER_RANGE = TWO.pow(64).subtract(TWO.pow(32));
   // 2**128 - 2**96
   static final BigInteger UPPER_RANGE = TWO.pow(128).subtract(TWO.pow(96));

   static final byte[] ALL_ZEROES = new byte[32];

   int streams;
   L1Hash32[] l1hash;

   // Constructor(s)
   // -------------------------------------------------------------------------

   /** Trivial 0-arguments constructor. */
   public UHash32() {
      super("uhash32");
   }

   /**
    * <p>Private constructor for cloning purposes.</p>
    *
    * @param that the instance to clone.
    */
   private UHash32(UHash32 that) {
      this();

      this.streams = that.streams;
      if (that.l1hash != null) {
//         this.l1hash = new L1Hash32[that.l1hash.length];
         this.l1hash = new L1Hash32[that.streams];
//         for (int i = 0; i < that.l1hash.length; i++) {
         for (int i = 0; i < that.streams; i++) {
            if (that.l1hash[i] != null) {
               this.l1hash[i] = (L1Hash32) that.l1hash[i].clone();
            }
         }
      }
   }

   // Class methods
   // -------------------------------------------------------------------------

   /**
    * <p>The prime numbers used in UMAC are:</p>
    * <pre>
    *   +-----+--------------------+---------------------------------------+
    *   |  x  | prime(x) [Decimal] | prime(x) [Hexadecimal]                |
    *   +-----+--------------------+---------------------------------------+
    *   | 19  | 2^19  - 1          | 0x0007FFFF                            |
    *   | 32  | 2^32  - 5          | 0xFFFFFFFB                            |
    *   | 36  | 2^36  - 5          | 0x0000000F FFFFFFFB                   |
    *   | 64  | 2^64  - 59         | 0xFFFFFFFF FFFFFFC5                   |
    *   | 128 | 2^128 - 159        | 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFF61 |
    *   +-----+--------------------+---------------------------------------+
    *</pre>
    *
    * @param n a number of bits.
    * @return the largest prime number less than 2**n.
    */
   static final BigInteger prime(int n) {
      switch (n) {
      case 19: return PRIME_19;
      case 32: return PRIME_32;
      case 36: return PRIME_36;
      case 64: return PRIME_64;
      case 128: return PRIME_128;
      default: throw new IllegalArgumentException("Undefined prime("+String.valueOf(n)+")");
      }
   }

   // Instance methods
   // -------------------------------------------------------------------------

   // java.lang.Cloneable interface implementation ----------------------------

   public Object clone() {
      return new UHash32(this);
   }

   // gnu.crypto.mac.IMac interface implementation ----------------------------

   public int macSize() {
      return UMac32.OUTPUT_LEN;
   }

   public void init(Map attributes)
   throws InvalidKeyException, IllegalStateException {
      byte[] K = (byte[]) attributes.get(MAC_KEY_MATERIAL);
      if (K == null) {
         throw new InvalidKeyException("Null Key");
      }
      if (K.length != UMac32.KEY_LEN) {
         throw new InvalidKeyException("Invalid Key length: "+String.valueOf(K.length));
      }

      // Calculate iterations needed to make UMAC-OUTPUT-LEN bytes
      streams = (UMac32.OUTPUT_LEN + 3) / 4;

      // Define total key needed for all iterations using UMacGenerator.
      // L1Key and L3Key1 both reuse most key between iterations.
      IRandom  kdf1 = new UMacGenerator();
      IRandom  kdf2 = new UMacGenerator();
      IRandom  kdf3 = new UMacGenerator();
      IRandom  kdf4 = new UMacGenerator();
      Map map = new HashMap();
      map.put(IBlockCipher.KEY_MATERIAL, K);
      map.put(UMacGenerator.INDEX, new Integer(0));
      kdf1.init(map);
      map.put(UMacGenerator.INDEX, new Integer(1));
      kdf2.init(map);
      map.put(UMacGenerator.INDEX, new Integer(2));
      kdf3.init(map);
      map.put(UMacGenerator.INDEX, new Integer(3));
      kdf4.init(map);

      // need to generate all bytes for use later in a Toepliz construction
      byte[] L1Key  = new byte[UMac32.L1_KEY_LEN + (streams - 1) * 16];
      try {
         kdf1.nextBytes(L1Key, 0, L1Key.length);
      } catch (LimitReachedException x) {
         x.printStackTrace(System.err);
         throw new RuntimeException("KDF for L1Key reached limit");
      }

      l1hash = new L1Hash32[streams];
      for (int i = 0; i < streams; i++) {
         byte[] k1 =  new byte[UMac32.L1_KEY_LEN];
         System.arraycopy(L1Key,  i * 16, k1,  0, UMac32.L1_KEY_LEN);
         byte[] k2 =  new byte[24];
         try {
            kdf2.nextBytes(k2, 0, 24);
         } catch (LimitReachedException x) {
            x.printStackTrace(System.err);
            throw new RuntimeException("KDF for L2Key reached limit");
         }

         byte[] k31 = new byte[64];
         try {
            kdf3.nextBytes(k31, 0, 64);
         } catch (LimitReachedException x) {
            x.printStackTrace(System.err);
            throw new RuntimeException("KDF for L3Key1 reached limit");
         }

         byte[] k32 = new byte[4];
         try {
            kdf4.nextBytes(k32, 0, 4);
         } catch (LimitReachedException x) {
            x.printStackTrace(System.err);
            throw new RuntimeException("KDF for L3Key2 reached limit");
         }

         L1Hash32 mac = new L1Hash32();
         mac.init(k1, k2, k31, k32);
         l1hash[i] = mac;
      }
   }

   public void update(byte b) {
      for (int i = 0; i < streams; i++) {
         l1hash[i].update(b);
      }
   }

   public void update(byte[] b, int offset, int len) {
      for (int i = 0; i < len; i++) {
         this.update(b[offset + i]);
      }
   }

   public byte[] digest() {
      byte[] result = new byte[UMac32.OUTPUT_LEN];
      for (int i = 0; i < streams; i++) {
         byte[] partialResult = l1hash[i].digest();
         System.arraycopy(partialResult, 0, result, 4*i, 4);
      }
      reset();
      return result;
   }

   public void reset() {
      for (int i = 0; i < streams; i++) {
         l1hash[i].reset();
      }
   }

   public boolean selfTest() {
      return true;
   }

   // helper methods ----------------------------------------------------------

   // Inner classes
   // =========================================================================

   /**
    * First hash stage of the UHash32 algorithm.
    */
   class L1Hash32 implements Cloneable {

      // Constants and variables
      // ----------------------------------------------------------------------

      private int[] key; // key material as an array of 32-bit ints
      private byte[] buffer; // work buffer L1_KEY_LEN long
      private int count; // meaningful bytes in buffer
      private ByteArrayOutputStream Y;
//      private byte[] y;
      private long totalCount;
      private L2Hash32 l2hash;
      private L3Hash32 l3hash;

      // Constructor(s)
      // ----------------------------------------------------------------------

      /** Trivial 0-arguments constructor. */
      L1Hash32() {
         super();

         key = new int[UMac32.L1_KEY_LEN / 4];
         buffer = new byte[UMac32.L1_KEY_LEN];
         count = 0;
         Y = new ByteArrayOutputStream();
         totalCount = 0L;
      }

      /**
       * <p>Private constructor for cloning purposes.</p>
       *
       * @param that the instance to clone.
       */
      private L1Hash32(L1Hash32 that) {
         this();

         System.arraycopy(that.key, 0, this.key, 0, that.key.length);
         System.arraycopy(that.buffer, 0, this.buffer, 0, that.count);
         this.count = that.count;
         byte[] otherY = that.Y.toByteArray();
         this.Y.write(otherY, 0, otherY.length);
         this.totalCount = that.totalCount;
         if (that.l2hash != null) {
            this.l2hash = (L2Hash32) that.l2hash.clone();
         }
         if (that.l3hash != null) {
            this.l3hash = (L3Hash32) that.l3hash.clone();
         }
      }

      // Class methods
      // ----------------------------------------------------------------------

      // Instance methods
      // ----------------------------------------------------------------------

      // java.lang.Cloneable interface implementation -------------------------

      public Object clone() {
         return new L1Hash32(this);
      }

      // other instance methods -----------------------------------------------

      public void init(byte[] k1, byte[] k2, byte[] k31, byte[] k32) {
         for (int i = 0, j = 0; i < (UMac32.L1_KEY_LEN / 4); i++) {
            key[i] = k1[j++]         << 24 |
                    (k1[j++] & 0xFF) << 16 |
                    (k1[j++] & 0xFF) <<  8 |
                    (k1[j++] & 0xFF);
         }

         l2hash = new L2Hash32(k2);
         l3hash = new L3Hash32(k31, k32);
      }

      public void update(byte b) {
         // Break M into L1_KEY_LEN byte chunks (final chunk may be shorter)

         // Let M_1, M_2, ..., M_t be strings so that M = M_1 || M_2 || .. ||
         // M_t, and length(M_i) = L1_KEY_LEN for all 0 < i < t.

         // For each chunk, except the last: endian-adjust, NH hash
         // and add bit-length.  Use results to build Y.
         buffer[count] = b;
         count++;
         totalCount++;
         if (count >= UMac32.L1_KEY_LEN) {
            byte[] y = nh32(UMac32.L1_KEY_LEN);
            Y.write(y, 0, 8);

            count = 0;

            // For each iteration, extract key and three-layer hash.
            // If length(M) <= L1_KEY_LEN, then skip L2-HASH.
            if (Y.size() == 16) { // we already hashed twice L1_KEY_LEN
               byte[] A = Y.toByteArray();
               Y.reset();
               l2hash.update(A, 0, 16);
            }
         }
      }

      public byte[] digest() {
         // For the last chunk: pad to 32-byte boundary, endian-adjust,
         // NH hash and add bit-length.  Concatenate the result to Y.
         if (count != 0) {
            if (count % 32 != 0) {
               int limit = 32 * ((count + 31) / 32);
               System.arraycopy(ALL_ZEROES, 0, buffer, count, limit - count);
               count += limit - count;
            }
            byte[] y = nh32(count);
            Y.write(y, 0, 8);
         }

         byte[] A = Y.toByteArray();
         Y.reset();
         byte[] B;
         if (totalCount <= UMac32.L1_KEY_LEN) {
            // we might have 'update'd the bytes already. check
            if (A.length == 0) { // we did
               B = l2hash.digest();
            } else { // did not
               B = new byte[16];
               System.arraycopy(A, 0, B, 8, 8);
            }
         } else {
            if (A.length != 0) {
               l2hash.update(A, 0, A.length);
            }
            B = l2hash.digest();
         }

         byte[] result = l3hash.digest(B);
         reset();
         return result;
      }

      public void reset() {
         count = 0;
         Y.reset();
         totalCount = 0L;
         if (l2hash != null) {
            l2hash.reset();
         }
      }

      // helper methods -------------------------------------------------------

      /**
       * 5.1  NH-32: NH hashing with a 32-bit word size.
       *
       * @param len count of bytes, divisible by 32, in buffer to process
       * @return Y, string of length 8 bytes.
       */
      private byte[] nh32(int len) {
         // Break M and K into 4-byte chunks
         int t = len / 4;

         // Let M_1, M_2, ..., M_t be 4-byte strings
         // so that M = M_1 || M_2 || .. || M_t.
         // Let K_1, K_2, ..., K_t be 4-byte strings
         // so that K_1 || K_2 || .. || K_t  is a prefix of K.
         int[] m = new int[t];

         int i;
         int j = 0;
         for (i = 0, j = 0; i < t; i++) {
            m[i] = buffer[j++]         << 24 |
                  (buffer[j++] & 0xFF) << 16 |
                  (buffer[j++] & 0xFF) <<  8 |
                  (buffer[j++] & 0xFF);
         }

         // Perform NH hash on the chunks, pairing words for multiplication
         // which are 4 apart to accommodate vector-parallelism.
         long result = len * 8L;
         for (i = 0; i < t; i += 8) {
            result += ((m[i+0] + key[i+0]) & 0xFFFFFFFFL) * ((m[i+4] + key[i+4]) & 0xFFFFFFFFL);
            result += ((m[i+1] + key[i+1]) & 0xFFFFFFFFL) * ((m[i+5] + key[i+5]) & 0xFFFFFFFFL);
            result += ((m[i+2] + key[i+2]) & 0xFFFFFFFFL) * ((m[i+6] + key[i+6]) & 0xFFFFFFFFL);
            result += ((m[i+3] + key[i+3]) & 0xFFFFFFFFL) * ((m[i+7] + key[i+7]) & 0xFFFFFFFFL);
         }

         return new byte[] {
            (byte)(result >>> 56), (byte)(result >>> 48),
            (byte)(result >>> 40), (byte)(result >>> 32),
            (byte)(result >>> 24), (byte)(result >>> 16),
            (byte)(result >>>  8), (byte) result
         };
      }
   }

   // =========================================================================

   /**
    * <p>Second hash stage of the UHash32 algorithm.</p>
    *
    * 5.4  L2-HASH-32: Second-layer hash.<p>
    * <ul>
    *    <li>Input:<br>
    *       K string of length 24 bytes.<br>
    *       M string of length less than 2^64 bytes.</li>
    *    <li>Returns:<br>
    *       Y, string of length 16 bytes.</li>
    * </ul>
    */
   class L2Hash32 implements Cloneable {

      // Constants and variables
      // ----------------------------------------------------------------------

      private BigInteger k64, k128;
      private BigInteger y;
      private boolean highBound;
      private long bytesSoFar;
      private ByteArrayOutputStream buffer;

      // Constructor(s)
      // ----------------------------------------------------------------------

      L2Hash32(byte[] K) {
         super();

         if (K.length != 24) {
            throw new ExceptionInInitializerError("K length is not 24");
         }

         //  Extract keys and restrict to special key-sets
//         Mask64  = uint2str(0x01FFFFFF01FFFFFF, 8);
//         Mask128 = uint2str(0x01FFFFFF01FFFFFF01FFFFFF01FFFFFF, 16);
//         k64    = str2uint(K[1..8]  and Mask64);
//         k128   = str2uint(K[9..24] and Mask128);
         int i = 0;
         k64 = new BigInteger(1, new byte[] {
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF)
         });
         k128 = new BigInteger(1, new byte[] {
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0x01), (byte)(K[i++] & 0xFF),
            (byte)(K[i++] & 0xFF), (byte)(K[i++] & 0xFF)
         });

         y = BigInteger.ONE;
         highBound = false;
         bytesSoFar = 0L;
      }

      private L2Hash32(L2Hash32 that) {
         super();

         this.k64 = that.k64;
         this.k128 = that.k128;
         this.y = that.y;
         this.highBound = that.highBound;
         this.bytesSoFar = that.bytesSoFar;
         if (that.buffer != null) {
            byte[] thatbuffer = that.buffer.toByteArray();
            this.buffer = new ByteArrayOutputStream();
            this.buffer.write(thatbuffer, 0, thatbuffer.length);
         }
      }

      // Class methods
      // ----------------------------------------------------------------------

      // Instance methods
      // ----------------------------------------------------------------------

      // java.lang.Cloneable interface implementation -------------------------

      public Object clone() {
         return new L2Hash32(this);
      }

      // other instance methods -----------------------------------------------

      // this is called with either 8-bytes or 16-bytes
      void update(byte[] b, int offset, int len) {
         if (len == 0) {
            return;
         }

         if (!highBound) { // do the first (only?) 8-bytes
            poly(64, LOWER_RANGE, k64, b, offset, 8);
            bytesSoFar += 8L;
            highBound = (bytesSoFar > BOUNDARY);
            if (highBound) { // if we just crossed the limit then process y
               poly(128, UPPER_RANGE, k128, yTo16bytes(), 0, 16);
               buffer = new ByteArrayOutputStream();
            }
            // do the rest if any
            update(b, offset + 8, len -8);
         } else { // we're already beyond the 2**17 bytes size limit
            // process in chuncks of 16
            buffer.write(b, offset, len);
            if (buffer.size() > 16) {
               byte[] bb = buffer.toByteArray();
               poly(128, UPPER_RANGE, k128, bb, 0, 16);
               if (bb.length > 16) {
                  buffer.write(bb, 16, bb.length - 16);
               }
            }
         }
      }

      byte[] digest() {
         // If M no more than 2^17 bytes, hash under 64-bit prime,
         // otherwise, hash first 2^17 bytes under 64-bit prime and
         // remainder under 128-bit prime.
         if (!highBound) { // y is up-to-date
            // do nothing
         } else { // we may have some bytes in buffer
            byte[] bb = buffer.toByteArray();
            byte[] lastBlock = new byte[16];
            System.arraycopy(bb, 0, lastBlock, 0, bb.length);
            lastBlock[bb.length] = (byte) 0x80;
            poly(128, UPPER_RANGE, k128, lastBlock, 0, 16);
         }

         byte[] result = yTo16bytes();
         reset();
         return result;
      }

      void reset() {
         y = BigInteger.ONE;
         highBound = false;
         bytesSoFar = 0L;
         if (buffer != null) {
            buffer.reset();
         }
      }

      // helper methods -------------------------------------------------------

      private byte[] yTo16bytes() {
         byte[] yy = y.toByteArray();
         byte[] result = new byte[16];
         if (yy.length > 16) {
            System.arraycopy(yy, yy.length - 16, result, 0, 16);
         } else {
            System.arraycopy(yy, 0, result, 16 - yy.length, yy.length);
         }

         return result;
      }

      /**
       * 5.3  POLY: Polynomial hash
       * Function Name: POLY
       *
       * @param wordbits positive integer divisible by 8: called with 64 or 128.
       * @param maxwordrange positive integer less than 2**wordbits.
       * @param k integer in the range 0 .. prime(wordbits) - 1.
       * @param M string with length divisible by (wordbits / 8) bytes.
       * return y, integer in the range 0 .. prime(wordbits) - 1.
       */
      private void poly(int wordbits, BigInteger maxwordrange, BigInteger k,
                        byte[] M, int off, int len) {
         byte[] mag = new byte[len];
         System.arraycopy(M, off, mag, 0, len);
         // Define constants used for fixing out-of-range words
//         int wordbytes = wordbits / 8;

         BigInteger p = prime(wordbits);
         BigInteger offset = TWO.pow(wordbits).subtract(p); // 2^wordbits - p;
         BigInteger marker = p.subtract(BigInteger.ONE);

         // Break M into chunks of length wordbytes bytes
//         long n = M.length / wordbytes;
         // Let M_1, M_2, ..., M_n be strings of length wordbytes bytes
         // so that M = M_1 || M_2 || .. || M_n

         // For each input word, compare it with maxwordrange.  If larger
         // then hash the words 'marker' and (m - offset), both in range.
//         for (int i = 0; i < n; i++) {
            BigInteger m = new BigInteger(1, mag);
            if (m.compareTo(maxwordrange) >= 0) { // m >= maxwordrange
               y = y.multiply(k).add(marker).mod(p); // (k * y + marker) % p;
               y = y.multiply(k).add(m.subtract(offset)).mod(p); // (k * y + (m - offset)) % p;
            } else {
               y = y.multiply(k).add(m).mod(p); // (k * y + m) % p;
            }
//         }

//         return y;
      }
   }

   // =========================================================================

   /**
    * Third hash stage of the UHash32 algorithm.
    *
    * Input:
    *    K1 string of length 64 bytes.
    *    K2 string of length 4 bytes.
    *    M string of length 16 bytes.
    * Returns:
    *    Y, string of length 4 bytes.
    */
   class L3Hash32 implements Cloneable {

      // Constants and variables
      // ----------------------------------------------------------------------

      private static final long PRIME_36 = 0x0000000FFFFFFFFBL;

      private int[] k = new int[9];

      // Constructor(s)
      // ----------------------------------------------------------------------

      /**
       *
       * @param K1 string of length 64 bytes.
       * @param K2 string of length 4 bytes.
       */
      L3Hash32(byte[] K1, byte[] K2) {
         super();

         // pre-conditions
         if (K1.length != 64) {
            throw new ExceptionInInitializerError("K1 length is not 64");
         }
         if (K2.length != 4) {
            throw new ExceptionInInitializerError("K2 length is not 4");
         }

         // Break K1 into 8 chunks and convert to integers
//         int i = 0;
//         for (int j = 0; i < 8; ) {
         for (int i = 0, j = 0; i < 8; i++) {
            long kk = (K1[j++] & 0xFFL) << 56 |
                      (K1[j++] & 0xFFL) << 48 |
                      (K1[j++] & 0xFFL) << 40 |
                      (K1[j++] & 0xFFL) << 32 |
                      (K1[j++] & 0xFFL) << 24 |
                      (K1[j++] & 0xFFL) << 16 |
                      (K1[j++] & 0xFFL) <<  8 |
                      (K1[j++] & 0xFFL);
//            k[i++] = (int)(kk % PRIME_36);
            k[i] = (int)(kk % PRIME_36);
         }
//         k[i] = K2[0] << 24 | (K2[1] & 0xFF) << 16 | (K2[2] & 0xFF) << 8 | (K2[3] & 0xFF);
         k[8] = K2[0] << 24 | (K2[1] & 0xFF) << 16 | (K2[2] & 0xFF) << 8 | (K2[3] & 0xFF);
      }

      private L3Hash32(int[] k) {
         super();

         this.k = k;
      }

      // Class methods
      // ----------------------------------------------------------------------

      // Instance methods
      // ----------------------------------------------------------------------

      // java.lang.Cloneable interface implementation -------------------------

      public Object clone() {
         return new L3Hash32((int[]) k.clone());
      }

      // other instance methods -----------------------------------------------

      /**
       * @param M string of length 16 bytes.
       * @return Y, string of length 4 bytes.
       */
      byte[] digest(byte[] M) {
         if (M.length != 16) {
            throw new IllegalArgumentException("M length is not 16");
         }

         long m, y = 0L;
         for (int i = 0, j = 0; i < 8; i++) {
            // Break M into 8 chunks and convert to integers
            m = (M[j++] & 0xFFL) << 8 | (M[j++] & 0xFFL);

            // Inner-product hash, extract last 32 bits and affine-translate
//            y = (m_1 * k_1 + ... + m_8 * k_8) mod prime(36);
//            y = y mod 2^32;
            y += (m * (k[i] & 0xFFFFFFFFL)) % PRIME_36;
         }
         int Y = ((int) y) ^ k[8];
         return new byte[] {(byte)(Y >>> 24), (byte)(Y >>> 16), (byte)(Y >>> 8), (byte) Y};
      }
   }
}
