title: CubeHash. Pure PHP implementation. Multibyte block issue (CubeHash160+16/32+160-256)
body: <p>I'm newbie in cryptography.</p>
  <p>I'm trying to implement CubeHash hashing function on pure PHP.
  I've ported some JS-version (32-bit) to PHP (64-bit), but I'm not able to implement CubeHash with param <code>b</code> when it is grater than 1 (block size is grater than 1 byte).</p>
  <p>Please, help me.</p>
  <p>run online: <a href="https://repl.it/repls/FreshWickedHashfunction" rel="nofollow noreferrer">https://repl.it/repls/FreshWickedHashfunction</a></p>
  <pre><code>&lt;?php
  
  // repo: https://github.com/windlace/cubehash
  // based on https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
  
  namespace Cast\Crypto\CubeHash;
  
  // Cubehash 8/1-256 (CubeHash80+8/1+80-256)
  //
  // CubeHashi+r/b+f-h
  //
  // http://cubehash.cr.yp.to
  // http://en.wikipedia.org/wiki/CubeHash
  //
  // example-1: https://github.com/RndPhrase/cubehash.js/blob/master/cubehash.js
  // example-2: https://github.com/tearsofphoenix/cubehash/blob/master/index.js
  
  function cubehash256($r, $b, $string)
  {
      return CubeHash256::hash($r, $b, $string);
  }
  
  // returns 32-bit representation on 64-bit integer
  function i32($value)
  {
      $value = ($value &amp; 0xFFFFFFFF);
      if ($value &amp; 0x80000000) $value = -((~$value &amp; 0xFFFFFFFF) + 1);
      return $value;
  }
  
  class CubeHash256
  {   
  
      /**
       * Init vector computing by 10r rounds as described in the specification.
       *
       * @param   integer $r  The number of rounds per block
       * @param   integer $b  The block size in bytes, defined for {1, 2, 3, ... 128}
       * @param   integer $h  The size of the hash output in bits, defined for {8, 16, 24, 32, ... 512}
       *
       * @return array
       */
      public static function iv($r, $b, $h)
      {
          $initial_state = array_fill(0, 32, 0);
          $initial_state[0] = $h/8;
          $initial_state[1] = $b;
          $initial_state[2] = $r;
  
          // init state
          $state = new \SplFixedArray(32);
  
          for ($i = 0; $i &lt; 32; $i += 1) {
              $state[$i] = $initial_state[$i];
          }
  
          // finalize (10*r)
          for ($i = 0; $i &lt; 10; $i += 1) {
              self::transform($r, $state);
          }
  
          return $state-&gt;toArray();
      }
  
      /**
       * @param   integer $r  The number of rounds per block
       * @param $b
       * @param $data
       * @return string
       */
      public static function hash($r, $b, $data)
      {
          // init state
          $state = new \SplFixedArray(32);
  
          $iv = self::iv($r, $b, 256);
          
          for ($i = 0; $i &lt; 32; $i += 1) {
              $state[$i] = $iv[$i];
          }
  
          // update with data
          $data .= chr(128);
  
          foreach (str_split($data, $b) as $block) {
              $blockHex = unpack('H*', $block);
              $blockDec = hexdec($blockHex[1]);
              $state[0] ^= $blockDec;
              $state = self::transform($r, $state);
          }
  
          // finalize
          $state[31] ^= 1;
  
          for ($i = 0; $i &lt; 10; $i += 1) {
              self::transform($r, $state);
          }
  
          // Example for '' (empty string hash)
  //        $state = [
  //              -1561800392 =&gt; '38d1e8a2',
  //               -961905875 =&gt; '2d7baac6',
  //               -664644867 =&gt; 'fd5262d8',
  //              -1399003075 =&gt; '3de89cac',
  //              -3546249993 =&gt; 'f784a02c',
  //              -3399252310 =&gt; 'aa866335',
  //              -2373478103 =&gt; '29998772',
  //              -2789414358 =&gt; '2aeabc59',
  //              -1685548184 =&gt; '6893889b',
  //              -3641377267 =&gt; '0dfef426',
  //              -1599741972 =&gt; 'ecdfa5a0',
  //              -3971411240 =&gt; 'd8124913',
  //              -3644175510 =&gt; '6a4bca26',
  //              -2826556663 =&gt; '092b8657',
  //               -854482381 =&gt; '33a211cd',
  //              -3508776574 =&gt; '8251dc2e',
  //            -370904486330 =&gt; '46ae5ea4',
  //            -399992847312 =&gt; '308491de',
  //            -333955865119 =&gt; 'e1e5ad3e',
  //            -367735441682 =&gt; 'ee764261',
  //            -358144429942 =&gt; '8ab0ed9c',
  //            -372630171200 =&gt; 'c0d1823d',
  //            -347016896839 =&gt; 'b95e2e34',
  //            -362137648016 =&gt; '7004eaae',
  //            -386413349251 =&gt; '7d36f807',
  //            -350848758866 =&gt; 'aecbc84f',
  //            -386257376189 =&gt; '432c4411',
  //            -383406607732 =&gt; '8c722fbb',
  //            -360378781490 =&gt; 'ce30c017',
  //            -385614396406 =&gt; '0a449737',
  //            -401364645913 =&gt; 'e787cd8c',
  //            -359536410101 =&gt; '0bc2f549',
  //        ];
          // concat hex produces a hash : 38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59
  
          // convert to hex
          $s = '';
          for ($i = 0; $i &lt; 8; $i += 1) {
              $s .= self::signed2hex($state[$i], false);
          }
  
          return $s;
      }
  
      /**
       * @param   integer         $r      The number of rounds per block
       * @param   \SplFixedArray  $state  The main context
       * @return mixed
       */
      protected static function transform($r, $state)
      {
          $y = new \SplFixedArray(16);
  
          for ($ri = 0;$ri &lt; $r; $ri += 1) {
              for ($i = 0; $i &lt; 16; $i += 1) $state[$i + 16] += $y[$i^8] = $state[$i];
              for ($i = 0; $i &lt; 16; $i += 1) $state[$i]       = i32(self::rotate($y[$i],  7)^$state[$i + 16]);
              for ($i = 0; $i &lt; 16; $i += 1) $y[$i^2]         = $state[$i + 16];
              for ($i = 0; $i &lt; 16; $i += 1) $state[$i + 16]  = $y[$i] + $state[$i];
              for ($i = 0; $i &lt; 16; $i += 1) $y[$i^4]         = $state[$i];
              for ($i = 0; $i &lt; 16; $i += 1) $state[$i]       = i32(self::rotate($y[$i], 11)^$state[$i + 16]);
              for ($i = 0; $i &lt; 16; $i += 2) self::swap($state, $i + 16, $i + 17);
          }
  
          return $state;
      }
  
      protected static function rotate($a, $b)
      {
          return (0xffffffff00000000 | ($a &lt;&lt; $b)) | (($a &amp; 0x00000000ffffffff) &gt;&gt; (32 - $b));
      }
  
      protected static function swap($arr, $i, $j)
      {
          $tmp = $arr[$i];
          $arr[$i] = $arr[$j];
          $arr[$j] = $tmp;
  
          return $arr;
      }
  
      /**
       * Converts signed decimal to hex (Two's complement)
       *
       * @param $value int, signed
       *
       * @param $reverseEndianness bool, if true reverses the byte order (see machine dependency)
       *
       * @return string, upper case hex value, both bytes padded left with zeros
       */
      protected static function signed2hex($value, $reverseEndianness = true)
      {
          $packed = pack('i', $value);
          $hex='';
          for ($i=0; $i &lt; 4; $i++){
              $hex .= str_pad( dechex(ord($packed[$i])) , 2, '0', STR_PAD_LEFT);
          }
          $tmp = str_split($hex, 2);
          $out = implode('', ($reverseEndianness ? array_reverse($tmp) : $tmp));
          return $out;
      }
  }
  
  function padBlock($input, $blockSizeBytes)
  {
      $blockSize = $blockSizeBytes * 2;
      $input = bin2hex($input);
      $input = str_repeat('0', $blockSize - ((strlen($input) % $blockSize) ?: $blockSize)) . $input;
      $input = !strlen($input) ? str_repeat('0', $blockSize) : $input;
      return hex2bin($input);
  }
  
  function assertEquals($a, $b) {
      ($a === $b) or
        die(
          &quot;Equality assertion failed:\n\n&quot; .
          &quot;Expected:\n&quot; . (is_array($a) ? 'Array' : $a) . &quot;\n&quot;.
          &quot;Actual:\n&quot;   . (is_array($b) ? 'Array' : $b) . &quot;\n&quot;
        );
  }
  
  // KNOWN EXAPMLES:
  
  // CubeHash10+1/1+10-256:
  assertEquals(
      [
            -1364746004,   1413475564,   -896460311,  1495629273,
              831721974,  -1114895892,  -1131802667, -1345622233,
            -966112469,  -1678886309,   -691894012,  1358586066,
            -1854453552,  -1070683759,    748270806,  1529972269,
            2136745624,   4462657195,   9868789035,  6357691622,
            -4981802178,  -6422354290,  -1400617557, -2741214963,
            -272471494,   5329758627,   2868717903,   710720199,
            -709654222,   2807896093,   2249717683,  2710321235,
      ],
      CubeHash256::iv(1, 1, 256)
  );
  
  assertEquals('80f72e07d04ddadb44a78823e0af2ea9f72ef3bf366fd773aa1fa33fc030e5cb', cubehash256(1, 1, ''));
  
  assertEquals('f63041a946aa98bd47f3175e6009dcb2ccf597b2718617ba46d56f27ffe35d49', cubehash256(1, 1, 'Hello'));
  
  assertEquals('217a4876f2b24cec489c9171f85d53395cc979156ea0254938c4c2c59dfdf8a4', cubehash256(1, 1, 'The quick brown fox jumps over the lazy dog'));
  
  // CubeHash80+8/1+80-256
  assertEquals(
      [
            -2096419883,    658334063,   -679114902,  1246757400,
            -1523021469,   -289996037,   1196718146,  1168084361,
            -2027445816,  -1170162360,   -822837272,   625197683,
            1543712850,  -1365258909,    759513533,  -424228083,
          -13765010209,  -2824905881,  -9887154026, 19352563566,
            5669379852, -31581549269,  21359466527, 10648459874,
            -8561790247,   9779462261, -22434204802, -4124492433,
            19608647852,   9541915967,   5144979599, -4355863926,
      ],
      CubeHash256::iv(8, 1, 256)
  );
  
  assertEquals('38d1e8a22d7baac6fd5262d83de89cacf784a02caa866335299987722aeabc59', cubehash256(8, 1, ''));
  
  assertEquals('692638db57760867326f851bd2376533f37b640bd47a0ddc607a9456b692f70f', cubehash256(8, 1, 'Hello'));
  
  assertEquals('94e0c958d85cdfaf554919980f0f50b945b88ad08413e0762d6ff0219aff3e55', cubehash256(8, 1, 'The quick brown fox jumps over the lazy dog'));
  
  // CubeHash160+16/32+160-256
  assertEquals(
      [
            -366226252,   -858328417,   1662090865,    893918894,
              575745371,   -438743453,   2120368433,   -187952450,
            -1026509162,   1118773360,   -797832139,    862050956,
              684518564,  -1896305277,   1182837760,   1088813995,
            7928299971,   5922880469, -36834009791, -21731580295,
          -42794932919,  35964212739,  -2587323651, -57649962363,
            5015516590, -27409035680, -45243252771,  58068387621,
          -29703972117,   5548452566, -40318076753, -30769206262,
      ],
      CubeHash256::iv(16, 32, 256)
  );
  
  // multybyte-blocks for an empty string works propertly.
  assertEquals('44c6de3ac6c73c391bf0906cb7482600ec06b216c7c54a2a8688a6a42676577d', cubehash256(16, 32, ''));
  
  // padding with 0 works propertly
  assertEquals('e27007aa498dd2100ffc76ce4eff578e6eb89908967186156f065cf6a61f6855', cubehash256(16, 32, padBlock('', 32)));
  
  // NOT WORKING
  assertEquals('e712139e3b892f2f5fe52d0f30d78a0cb16b51b217da0e4acb103dd0856f2db0', cubehash256(16, 32, 'Hello'));
  
  // padding with 0 is NOT WORKING
  assertEquals('72070e821ab48ae56cb693c1e59676a9d4c430b0a9321adedea5f00c825c9f69', cubehash256(16, 32, padBlock('Hello', 32)));
  
  // NOT WORKING
  assertEquals('3632b64528815b66875e7feb9be68fe3e0e502dd405d7910c23f16e6b6ffeef7', cubehash256(16, 32, hex2bin('8072a313bb0819a9874277b11303e252f3af7a229a407c1a618e8e4f38af6ca3')));
  
  // NOT WORKING
  assertEquals('5151e251e348cbbfee46538651c06b138b10eeb71cf6ea6054d7ca5fec82eb79', cubehash256(16, 32, 'The quick brown fox jumps over the lazy dog'));
  
  echo &quot;Done.&quot;;
  
  </code></pre>
  <h3>Update:</h3>
  <p>From documentation:</p>
  <blockquote>
  <p>CubeHash maintains a 128-byte state. It xors the first b-byte input
  block into the first b bytes of the state, transforms the state
  invertibly through r identical rounds, xors the next b-byte input
  block into the first b bytes of the state, transforms the state
  invertibly through r identical rounds, xors the next b-byte input
  block into the first b bytes of the state, transforms the state
  invertibly through r identical rounds, etc.</p>
  </blockquote>