1 // Written in the D programming language.
2 
3 /**
4 This class provides an implementation of the SipHash cryptographic function.
5 Implementation is based on the following code (by Károly Lőrentey): https://github.com/attaswift/SipHash/blob/master/SipHash/SipHasher.swift
6 
7 Copyright: LightHouse Software, 2021
8 License:   $(HTTP https://github.com/aquaratixc/ESL-License, Experimental Software License 1.0).
9 Authors:   Oleg Bakharev,
10 		   Ilya Pertsev
11 
12 See also: 
13 	https://131002.net/siphash, https://github.com/attaswift/SipHash/blob/master/SipHash/SipHasher.swift		   
14 */
15 module styx2000.extrautil.siphash;
16 
17 /**
18 	The class provides functionality to initialize the internal state of the SipHash hash function and generate a hash for byte arrays.
19 	Params:
20 	NUMBER_OF_COMPRESS_ROUNDS = Number of compression rounds (default: 2)
21 	NUMBER_OF_FINALIZATION_ROUNDS = Number of finalization rounds (default: 4)
22 */
23 class SipHash(ubyte NUMBER_OF_COMPRESS_ROUNDS = 2, ubyte NUMBER_OF_FINALIZATION_ROUNDS = 4)
24 {
25     private
26     {
27         /// Secret key: 0 - low part of key, 1 - high of key
28         ulong key0, key1;
29         
30         /// Internal state
31         ulong v0 = 0x736f6d6570736575;
32         ulong v1 = 0x646f72616e646f6d;
33         ulong v2 = 0x6c7967656e657261;
34         ulong v3 = 0x7465646279746573;
35 
36         ulong pendingBytes = 0;
37         ulong pendingByteCount = 0;
38         long byteCount = 0;
39     }
40 
41     private
42     {
43 		/**
44 		Constructs (restores) a unsigned long value (little-endian order of bytes in value) from the passed pointer to ubyte array. 
45 		For internal use.
46 	    Params:
47 		ptr = Pointer to unsigned byte array.
48 	  
49 	    */
50         ulong _toLEUlong(ubyte* ptr) @system @nogc
51         {
52             ulong h = 0x0000000000000000;
53 
54             h |= (cast(ulong) ptr[0]);
55             h |= (cast(ulong) ptr[1]) << 8;
56             h |= (cast(ulong) ptr[2]) << 16;
57             h |= (cast(ulong) ptr[3]) << 24;
58             h |= (cast(ulong) ptr[4]) << 32;
59             h |= (cast(ulong) ptr[5]) << 40;
60             h |= (cast(ulong) ptr[6]) << 48;
61             h |= (cast(ulong) ptr[7]) << 56;
62 
63             return h;
64         }
65 
66 		/**
67 		Rotated shift of unsigned long value. 
68 		For internal use.
69 	    Params:
70 		value = Unsigned long value.
71 		amount = Number of position to shift.
72 	  
73 	    */
74         ulong _rotateLeft(ulong value, ulong amount) @nogc
75         {
76             return (value << amount) | (value >> (64 - amount));
77         }
78 
79 		/**
80 		One SipHash round. 
81 		For internal use.
82 	  
83 	    */
84         void _round() @nogc
85         {
86             v0 += v1;
87             v1 = _rotateLeft(v1, 13);
88             v1 ^= v0;
89             v0 = _rotateLeft(v0, 32);
90             v2 += v3;
91             v3 = _rotateLeft(v3, 16);
92             v3 ^= v2;
93             v0 += v3;
94             v3 = _rotateLeft(v3, 21);
95             v3 ^= v0;
96             v2 += v1;
97             v1 = _rotateLeft(v1, 17);
98             v1 ^= v2;
99             v2 = _rotateLeft(v2, 32);
100         }
101 
102 
103 		/**
104 		Compress one word. 
105 		For internal use.
106 	    Params:
107 		m = Unsigned long value (word for compressing).
108 	  
109 	    */
110         void _compress(ulong m) @nogc
111         {
112             v3 ^= m;
113             for (ubyte i = 0; i < NUMBER_OF_COMPRESS_ROUNDS; i++)
114             {
115                 _round;
116             }
117             v0 ^= m;
118         }
119 
120 		
121 		/**
122 		Finalization procedure.Needed for ending of hashing. 
123 		For internal use.
124 
125 	    */
126         ulong _finalize() @nogc
127         {
128             pendingBytes |= (cast(ulong) byteCount) << 56;
129             byteCount = -1;
130 
131             _compress(pendingBytes);
132 
133             v2 ^= 0xff;
134 
135             for (ubyte i = 0; i < NUMBER_OF_FINALIZATION_ROUNDS; i++)
136             {
137                 _round;
138             }
139 
140             return v0 ^ v1 ^ v2 ^ v3;
141         }
142     }
143 
144 	/**
145 	Default constructor. 
146 	Initializes the internal state of the function with a random 128-bit key (split into two 64-bit parts).
147 
148     */
149     this()
150     {
151         import std.random : Random, uniform, unpredictableSeed;
152 
153         auto rng = Random(unpredictableSeed);
154 
155         this(uniform(0UL, ulong.max, rng), uniform(0UL, ulong.max, rng));
156     }
157 
158 	/**
159 	Basic constructor. 
160 	Initializes the internal state with a 128-bit key, split into two 64-bit parts - the low-order and high-order parts of the key.
161 	Returns:
162 	key0 = low part of key.
163 	key1 = high part of key.
164 
165     */	
166     this(ulong key0, ulong key1)
167     {
168         key0 = key0;
169         key1 = key1;
170 
171         v0 ^= key0;
172         v1 ^= key1;
173         v2 ^= key0;
174         v3 ^= key1;
175     }
176 
177 	/**
178 	Append bytes to internal hash-function state.
179     Params:
180 	buffer = Unsigned byte array.
181   
182     */
183     void append(ubyte[] buffer...)
184     {
185         import std.algorithm : min;
186 
187         auto i = 0;
188         if (pendingByteCount > 0)
189         {
190             ulong readCount = min(buffer.length, 8 - pendingByteCount);
191             ulong m = 0;
192             switch (readCount)
193             {
194             case 7:
195                 m |= cast(ulong)(buffer[6]) << 48;
196                 goto case;
197             case 6:
198                 m |= cast(ulong)(buffer[5]) << 40;
199                 goto case;
200             case 5:
201                 m |= cast(ulong)(buffer[4]) << 32;
202                 goto case;
203             case 4:
204                 m |= cast(ulong)(buffer[3]) << 24;
205                 goto case;
206             case 3:
207                 m |= cast(ulong)(buffer[2]) << 16;
208                 goto case;
209             case 2:
210                 m |= cast(ulong)(buffer[1]) << 8;
211                 goto case;
212             case 1:
213                 m |= cast(ulong)(buffer[0]);
214                 break;
215             default:
216                 break;
217             }
218             
219             pendingBytes |= m << cast(ulong)(pendingByteCount << 3);
220             pendingByteCount += readCount;
221             i += readCount;
222 
223             if (pendingByteCount == 8)
224             {
225                 _compress(pendingBytes);
226                 pendingBytes = 0;
227                 pendingByteCount = 0;
228             }
229         }
230 
231         ulong left = (buffer.length - i) & 7;
232         ulong end = (buffer.length - i) - left;
233 
234         while (i < end)
235         {
236             ulong m = 0;
237             auto ptr = buffer[i .. i + 8].ptr;
238             _compress(_toLEUlong(ptr));
239             i += 8;
240         }
241 
242         switch (left)
243         {
244         case 7:
245             pendingBytes |= cast(ulong)(buffer[i + 6]) << 48;
246             goto case;
247         case 6:
248             pendingBytes |= cast(ulong)(buffer[i + 5]) << 40;
249             goto case;
250         case 5:
251             pendingBytes |= cast(ulong)(buffer[i + 4]) << 32;
252             goto case;
253         case 4:
254             pendingBytes |= cast(ulong)(buffer[i + 3]) << 24;
255             goto case;
256         case 3:
257             pendingBytes |= cast(ulong)(buffer[i + 2]) << 16;
258             goto case;
259         case 2:
260             pendingBytes |= cast(ulong)(buffer[i + 1]) << 8;
261             goto case;
262         case 1:
263             pendingBytes |= cast(ulong)(buffer[i]);
264             break;
265         default:
266             break;
267         }
268 
269         pendingByteCount = left;
270 
271         byteCount += buffer.length;
272     }
273 
274 	/**
275 	Perform finalization of hash-function and returns hash.
276 	Returns:
277 	64-bit hash of passed bytes
278   
279     */
280     ulong finalize()
281     {
282         return _finalize();
283     }
284 }
285 
286 /**
287 Hashes a byte stream with the specified cryptographic key
288 Params:
289 bytes = Array of unsigned bytes.
290 key = Array of two ulong for low and high parts of 128-bit key (default: zero key)
291 
292 Typical usage:
293 ----
294 auto hash = hash8([0x65, 0x67, 0x67, 0x00]);
295 ----
296 */
297 auto hash8(ubyte[] bytes, ulong[2] key = [0UL, 0UL])
298 {
299     SipHash!(2, 4) sh = new SipHash!(2, 4)(key[0], key[1]);
300 
301     sh.append(bytes);
302 
303     return sh.finalize;
304 }
305 
306 /**
307 Hashes a string with the specified cryptographic key
308 Params:
309 string = String for hashing.
310 key = Array of two ulong for low and high parts of 128-bit key (default: zero key)
311 
312 Typical usage:
313 ----
314 auto hash = hash8(`Sample`);
315 ----
316 */
317 auto hash8(string s, ulong[2] key = [0UL, 0UL])
318 {
319     SipHash!(2, 4) sh = new SipHash!(2, 4)(key[0], key[1]);
320 
321     sh.append((cast(ubyte[]) s));
322 
323     return sh.finalize;
324 }