1<?php
2
3if (class_exists('ParagonIE_Sodium_Core_SipHash', false)) {
4 return;
5}
6
7/**
8 * Class ParagonIE_SodiumCompat_Core_SipHash
9 *
10 * Only uses 32-bit arithmetic, while the original SipHash used 64-bit integers
11 */
12class ParagonIE_Sodium_Core_SipHash extends ParagonIE_Sodium_Core_Util
13{
14 /**
15 * @internal You should not use this directly from another application
16 *
17 * @param int[] $v
18 * @return int[]
19 *
20 */
21 public static function sipRound(array $v)
22 {
23 # v0 += v1;
24 list($v[0], $v[1]) = self::add(
25 array($v[0], $v[1]),
26 array($v[2], $v[3])
27 );
28
29 # v1=ROTL(v1,13);
30 list($v[2], $v[3]) = self::rotl_64((int) $v[2], (int) $v[3], 13);
31
32 # v1 ^= v0;
33 $v[2] = (int) $v[2] ^ (int) $v[0];
34 $v[3] = (int) $v[3] ^ (int) $v[1];
35
36 # v0=ROTL(v0,32);
37 list($v[0], $v[1]) = self::rotl_64((int) $v[0], (int) $v[1], 32);
38
39 # v2 += v3;
40 list($v[4], $v[5]) = self::add(
41 array((int) $v[4], (int) $v[5]),
42 array((int) $v[6], (int) $v[7])
43 );
44
45 # v3=ROTL(v3,16);
46 list($v[6], $v[7]) = self::rotl_64((int) $v[6], (int) $v[7], 16);
47
48 # v3 ^= v2;
49 $v[6] = (int) $v[6] ^ (int) $v[4];
50 $v[7] = (int) $v[7] ^ (int) $v[5];
51
52 # v0 += v3;
53 list($v[0], $v[1]) = self::add(
54 array((int) $v[0], (int) $v[1]),
55 array((int) $v[6], (int) $v[7])
56 );
57
58 # v3=ROTL(v3,21);
59 list($v[6], $v[7]) = self::rotl_64((int) $v[6], (int) $v[7], 21);
60
61 # v3 ^= v0;
62 $v[6] = (int) $v[6] ^ (int) $v[0];
63 $v[7] = (int) $v[7] ^ (int) $v[1];
64
65 # v2 += v1;
66 list($v[4], $v[5]) = self::add(
67 array((int) $v[4], (int) $v[5]),
68 array((int) $v[2], (int) $v[3])
69 );
70
71 # v1=ROTL(v1,17);
72 list($v[2], $v[3]) = self::rotl_64((int) $v[2], (int) $v[3], 17);
73
74 # v1 ^= v2;;
75 $v[2] = (int) $v[2] ^ (int) $v[4];
76 $v[3] = (int) $v[3] ^ (int) $v[5];
77
78 # v2=ROTL(v2,32)
79 list($v[4], $v[5]) = self::rotl_64((int) $v[4], (int) $v[5], 32);
80
81 return $v;
82 }
83
84 /**
85 * Add two 32 bit integers representing a 64-bit integer.
86 *
87 * @internal You should not use this directly from another application
88 *
89 * @param int[] $a
90 * @param int[] $b
91 * @return array<int, mixed>
92 */
93 public static function add(array $a, array $b)
94 {
95 /** @var int $x1 */
96 $x1 = $a[1] + $b[1];
97 /** @var int $c */
98 $c = $x1 >> 32; // Carry if ($a + $b) > 0xffffffff
99 /** @var int $x0 */
100 $x0 = $a[0] + $b[0] + $c;
101 return array(
102 $x0 & 0xffffffff,
103 $x1 & 0xffffffff
104 );
105 }
106
107 /**
108 * @internal You should not use this directly from another application
109 *
110 * @param int $int0
111 * @param int $int1
112 * @param int $c
113 * @return array<int, mixed>
114 */
115 public static function rotl_64($int0, $int1, $c)
116 {
117 $int0 &= 0xffffffff;
118 $int1 &= 0xffffffff;
119 $c &= 63;
120 if ($c === 32) {
121 return array($int1, $int0);
122 }
123 if ($c > 31) {
124 $tmp = $int1;
125 $int1 = $int0;
126 $int0 = $tmp;
127 $c &= 31;
128 }
129 if ($c === 0) {
130 return array($int0, $int1);
131 }
132 return array(
133 0xffffffff & (
134 ($int0 << $c)
135 |
136 ($int1 >> (32 - $c))
137 ),
138 0xffffffff & (
139 ($int1 << $c)
140 |
141 ($int0 >> (32 - $c))
142 ),
143 );
144 }
145
146 /**
147 * Implements Siphash-2-4 using only 32-bit numbers.
148 *
149 * When we split an int into two, the higher bits go to the lower index.
150 * e.g. 0xDEADBEEFAB10C92D becomes [
151 * 0 => 0xDEADBEEF,
152 * 1 => 0xAB10C92D
153 * ].
154 *
155 * @internal You should not use this directly from another application
156 *
157 * @param string $in
158 * @param string $key
159 * @return string
160 * @throws SodiumException
161 * @throws TypeError
162 */
163 public static function sipHash24($in, $key)
164 {
165 $inlen = self::strlen($in);
166
167 # /* "somepseudorandomlygeneratedbytes" */
168 # u64 v0 = 0x736f6d6570736575ULL;
169 # u64 v1 = 0x646f72616e646f6dULL;
170 # u64 v2 = 0x6c7967656e657261ULL;
171 # u64 v3 = 0x7465646279746573ULL;
172 $v = array(
173 0x736f6d65, // 0
174 0x70736575, // 1
175 0x646f7261, // 2
176 0x6e646f6d, // 3
177 0x6c796765, // 4
178 0x6e657261, // 5
179 0x74656462, // 6
180 0x79746573 // 7
181 );
182 // v0 => $v[0], $v[1]
183 // v1 => $v[2], $v[3]
184 // v2 => $v[4], $v[5]
185 // v3 => $v[6], $v[7]
186
187 # u64 k0 = LOAD64_LE( k );
188 # u64 k1 = LOAD64_LE( k + 8 );
189 $k = array(
190 self::load_4(self::substr($key, 4, 4)),
191 self::load_4(self::substr($key, 0, 4)),
192 self::load_4(self::substr($key, 12, 4)),
193 self::load_4(self::substr($key, 8, 4))
194 );
195 // k0 => $k[0], $k[1]
196 // k1 => $k[2], $k[3]
197
198 # b = ( ( u64 )inlen ) << 56;
199 $b = array(
200 $inlen << 24,
201 0
202 );
203 // See docblock for why the 0th index gets the higher bits.
204
205 # v3 ^= k1;
206 $v[6] ^= $k[2];
207 $v[7] ^= $k[3];
208 # v2 ^= k0;
209 $v[4] ^= $k[0];
210 $v[5] ^= $k[1];
211 # v1 ^= k1;
212 $v[2] ^= $k[2];
213 $v[3] ^= $k[3];
214 # v0 ^= k0;
215 $v[0] ^= $k[0];
216 $v[1] ^= $k[1];
217
218 $left = $inlen;
219 # for ( ; in != end; in += 8 )
220 while ($left >= 8) {
221 # m = LOAD64_LE( in );
222 $m = array(
223 self::load_4(self::substr($in, 4, 4)),
224 self::load_4(self::substr($in, 0, 4))
225 );
226
227 # v3 ^= m;
228 $v[6] ^= $m[0];
229 $v[7] ^= $m[1];
230
231 # SIPROUND;
232 # SIPROUND;
233 $v = self::sipRound($v);
234 $v = self::sipRound($v);
235
236 # v0 ^= m;
237 $v[0] ^= $m[0];
238 $v[1] ^= $m[1];
239
240 $in = self::substr($in, 8);
241 $left -= 8;
242 }
243
244 # switch( left )
245 # {
246 # case 7: b |= ( ( u64 )in[ 6] ) << 48;
247 # case 6: b |= ( ( u64 )in[ 5] ) << 40;
248 # case 5: b |= ( ( u64 )in[ 4] ) << 32;
249 # case 4: b |= ( ( u64 )in[ 3] ) << 24;
250 # case 3: b |= ( ( u64 )in[ 2] ) << 16;
251 # case 2: b |= ( ( u64 )in[ 1] ) << 8;
252 # case 1: b |= ( ( u64 )in[ 0] ); break;
253 # case 0: break;
254 # }
255 switch ($left) {
256 case 7:
257 $b[0] |= self::chrToInt($in[6]) << 16;
258 case 6:
259 $b[0] |= self::chrToInt($in[5]) << 8;
260 case 5:
261 $b[0] |= self::chrToInt($in[4]);
262 case 4:
263 $b[1] |= self::chrToInt($in[3]) << 24;
264 case 3:
265 $b[1] |= self::chrToInt($in[2]) << 16;
266 case 2:
267 $b[1] |= self::chrToInt($in[1]) << 8;
268 case 1:
269 $b[1] |= self::chrToInt($in[0]);
270 case 0:
271 break;
272 }
273 // See docblock for why the 0th index gets the higher bits.
274
275 # v3 ^= b;
276 $v[6] ^= $b[0];
277 $v[7] ^= $b[1];
278
279 # SIPROUND;
280 # SIPROUND;
281 $v = self::sipRound($v);
282 $v = self::sipRound($v);
283
284 # v0 ^= b;
285 $v[0] ^= $b[0];
286 $v[1] ^= $b[1];
287
288 // Flip the lower 8 bits of v2 which is ($v[4], $v[5]) in our implementation
289 # v2 ^= 0xff;
290 $v[5] ^= 0xff;
291
292 # SIPROUND;
293 # SIPROUND;
294 # SIPROUND;
295 # SIPROUND;
296 $v = self::sipRound($v);
297 $v = self::sipRound($v);
298 $v = self::sipRound($v);
299 $v = self::sipRound($v);
300
301 # b = v0 ^ v1 ^ v2 ^ v3;
302 # STORE64_LE( out, b );
303 return self::store32_le($v[1] ^ $v[3] ^ $v[5] ^ $v[7]) .
304 self::store32_le($v[0] ^ $v[2] ^ $v[4] ^ $v[6]);
305 }
306}
307