content: package im.actor.crypto.primitives.streebog;
  
  import im.actor.crypto.primitives.util.ByteStrings;
  
  public class StreebogDigest {
  
      private final int hashLength;
      private byte[] h = new byte[64];
      private byte[] m = new byte[64];
      private byte[] e = new byte[64];
  
      private byte[] tmpT = new byte[64];
      private byte[] tmpS = new byte[64];
      private byte[] tmpK = new byte[64];
  
      private int pt;
      private long n;
  
      public StreebogDigest(int hashLength) {
          this.hashLength = hashLength;
          reset();
      }
  
      public void reset() {
          // IV:: 01010.. for 256-bit hash, 0000... for 512-bit hash
          // memset(&sbx->h, hlen == 32 ? 0x01 : 0x00, 64);
          // memset(&sbx->e, 0x00, 64);
          for (int i = 0; i < 64; i++) {
              if (hashLength == 32) {
                  h[i] = (byte) 0x01;
              } else {
                  h[i] = (byte) 0x00;
              }
              e[i] = (byte) 0x00;
          }
  
          // sbx->pt = 63;
          // sbx->n = 0;
          pt = 63;
          n = 0;
      }
  
      public void update(byte[] in, int offset, int length) {
  
          // j = sbx->pt;
          int j = pt;
          for (int i = 0; i < length; i++) {
              m[j--] = in[offset + i];
  
              // compress
              // if (j < 0) {
              if (j < 0) {
                  // streebog_g(&sbx->h, &sbx->m, sbx->n);
                  streebog_g(h, m, n);
                  // sbx->n += 0x200;
                  n += 0x200;
  
                  // epsilon summation
                  // c = 0;
                  int c = 0;
  
                  // for (j = 63; j >= 0; j--) {
                  for (j = 63; j >= 0; j--) {
                      // c += sbx->e.b[j] + sbx->m.b[j];
                      c += (e[j] & 0xFF) + (m[j] & 0xFF);
                      // sbx->e.b[j] = c & 0xFF;
                      e[j] = (byte) (c & 0xFF);
                      // c >>= 8;
                      c >>= 8;
                  }
  
                  // j = 63;
                  j = 63;
              }
          }
  
          // sbx->pt = j;
          pt = j;
  
      }
  
      public void doFinal(byte[] out, int offset) {
          // pad the message and run final g
          // i = sbx->pt;
          int i = pt;
          // sbx->m.b[i--] = 0x01;
          m[i--] = 1;
          while (i >= 0) {
              // sbx->m.b[i--] = 0x00;
              m[i--] = 0;
          }
          // streebog_g(&sbx->h, &sbx->m, sbx->n);
          streebog_g(h, m, n);
  
          // epsilon summation
          int c = 0;
          for (i = 63; i >= 0; i--) {
              // c += sbx->e.b[i] + sbx->m.b[i];
              c += (e[i] & 0xFF) + (m[i] & 0xFF);
              // sbx->e.b[i] = c & 0xFF;
              e[i] = (byte) (c & 0xFF);
              // c >>= 8;
              c >>= 8;
          }
  
          // finalization n
          // memset(&sbx->m, 0x00, 64);
          for (int j = 0; j < 64; j++) {
              m[j] = (byte) 0x00;
          }
  
          // sbx->n += (63 - sbx->pt) << 3;      // total bits
          n += (63 - pt) << 3;
          for (i = 63; n > 0; i--) {
              // sbx->m.b[i] = sbx->n & 0xFF;
              m[i] = (byte) (n & 0xFF);
              // sbx->n >>= 8;
              n >>= 8;
          }
  
          // streebog_g(&sbx->h, &sbx->m, 0);
          // streebog_g(&sbx->h, &sbx->e, 0);
          streebog_g(h, m, 0);
          streebog_g(h, e, 0);
  
          // copy the result
          // memcpy(hash, &sbx->h, sbx->hlen);
          for (int j = 0; j < hashLength; j++) {
              out[offset + j] = h[j];
          }
  
          // clear out sensitive stuff
          reset();
      }
  
      //    #define SBOG_LPSti64 \
  //            (sbob_sl64[0][t.b[i]] ^     sbob_sl64[1][t.b[i + 8]] ^  \
  //    sbob_sl64[2][t.b[i + 16]] ^ sbob_sl64[3][t.b[i + 24]] ^ \
  //    sbob_sl64[4][t.b[i + 32]] ^ sbob_sl64[5][t.b[i + 40]] ^ \
  //    sbob_sl64[6][t.b[i + 48]] ^ sbob_sl64[7][t.b[i + 56]])
  
      private static long SBOG_LPSti64(byte[] t, int i) {
          return (StreebogTables.sbob_sl64[0][t[i] & 0xFF] ^
                  StreebogTables.sbob_sl64[1][t[i + 8] & 0xFF] ^
                  StreebogTables.sbob_sl64[2][t[i + 16] & 0xFF] ^
                  StreebogTables.sbob_sl64[3][t[i + 24] & 0xFF] ^
                  StreebogTables.sbob_sl64[4][t[i + 32] & 0xFF] ^
                  StreebogTables.sbob_sl64[5][t[i + 40] & 0xFF] ^
                  StreebogTables.sbob_sl64[6][t[i + 48] & 0xFF] ^
                  StreebogTables.sbob_sl64[7][t[i + 56] & 0xFF]);
      }
  
      private void streebog_g(byte[] h, byte[] m, long n) {
          // w512_t k, s, t;
          // k = LPS(h ^ n)
          // memcpy(&t, h, 64);
  
          copy(h, tmpT);
          // zero(tmpK);
          // zero(tmpS);
  
          // for (i = 63; n > 0; i--) {
          for (int i = 63; n > 0; i--) {
              // t.b[i] ^= n & 0xFF;
              tmpT[i] = (byte) (tmpT[i] ^ ((byte) (n & 0xFF)));
              // n >>= 8;
              n >>= 8;
          }
  
          for (int i = 0; i < 8; i++) {
              // k.q[i] = SBOG_LPSti64;
              setWord64(tmpK, i, SBOG_LPSti64(tmpT, i));
          }
  
          // s = m
          // memcpy(&s, m, 64);
          copy(m, tmpS);
          // s.setBytes(m.getBytes());
  
          for (int r = 0; r < 12; r++) {
              // s = LPS(s ^ k)
              for (int i = 0; i < 8; i++) {
                  // t.q[i] = s.q[i] ^ k.q[i];
                  setWord64(tmpT, i, getWord64(tmpS, i) ^ getWord64(tmpK, i));
              }
              for (int i = 0; i < 8; i++) {
                  // s.q[i] = SBOG_LPSti64;
                  setWord64(tmpS, i, SBOG_LPSti64(tmpT, i));
              }
  
              // k = LPS(k ^ c[i])
              for (int i = 0; i < 8; i++) {
                  // t.q[i] = k.q[i] ^ sbob_rc64[r][i];
                  setWord64(tmpT, i, getWord64(tmpK, i) ^ StreebogTables.sbob_rc64[r][i]);
              }
  
              for (int i = 0; i < 8; i++) {
                  // k.q[i] = SBOG_LPSti64;
                  setWord64(tmpK, i, SBOG_LPSti64(tmpT, i));
              }
          }
  
          for (int i = 0; i < 64; i++) {
              // h->q[i] ^= s.q[i] ^ k.q[i] ^ m->q[i];
              h[i] = (byte) ((h[i] ^ tmpS[i] ^ tmpK[i] ^ m[i]) & 0xFF);
          }
      }
  
      private void copy(byte[] from, byte[] dest) {
          System.arraycopy(from, 0, dest, 0, 64);
      }
  
      private long getWord64(byte[] value, int index) {
          return ByteStrings.bytesToLong(value, index * 8);
      }
  
      private void setWord64(byte[] dest, int index, long val) {
          ByteStrings.write(dest, index * 8, ByteStrings.longToBytes(val), 0, 8);
      }
  }
  