-
Notifications
You must be signed in to change notification settings - Fork 2
/
hash.ts
84 lines (71 loc) · 2.31 KB
/
hash.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { JSONValue } from '../typeHelpers'
/**
* ### hash(data)
*
* Create a hashed string representation of the passed in data.
*
* :warning: **This function is not cryptographically secure, use
* [`Argon2id`, `scrypt` or `bcrypt`](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#password-hashing-algorithms)
* for anything security related.**
*
* ```js
* flocky.hash('some really long string')
* // -> 'x1nr7uiv'
*
* flocky.hash({id: 'AAA', name: 'BBB'})
* // -> 'x16mynva'
* ```
*
* <details>
* <summary>Implementation Details</summary>
*
* This method uses Murmur3 because it is small, fast and has fairly good
* collision characteristics (about 1 in 36000).
*
* - https://softwareengineering.stackexchange.com/questions/49550
* - https://github.com/VowpalWabbit/vowpal_wabbit/wiki/murmur2-vs-murmur3
* - https://en.wikipedia.org/wiki/MurmurHash
* - https://github.com/whitequark/murmurhash3-js/blob/master/murmurhash3.js
* </details>
*/
export function hash(data: JSONValue): string {
// Convert any data into a string
data = JSON.stringify(data)
// Setup length, seed and chunk looping
const len = data.length
let hash = len ^ len
const roundedEnd = len & ~0x1
// Go through 4-byte chunks
for (let i = 0; i < roundedEnd; i += 2) {
let chunk = data.charCodeAt(i) | (data.charCodeAt(i + 1) << 16)
chunk = mul32(chunk, 0xcc9e2d51)
chunk = ((chunk & 0x1ffff) << 15) | (chunk >>> 17)
chunk = mul32(chunk, 0x1b873593)
hash ^= chunk
hash = ((hash & 0x7ffff) << 13) | (hash >>> 19)
hash = (hash * 5 + 0xe6546b64) | 0
}
// Handle remaining bytes
if (len % 2 === 1) {
let remaining = data.charCodeAt(roundedEnd)
remaining = mul32(remaining, 0xcc9e2d51)
remaining = ((remaining & 0x1ffff) << 15) | (remaining >>> 17)
remaining = mul32(remaining, 0x1b873593)
hash ^= remaining
}
// Finalize
hash ^= len << 1
hash ^= hash >>> 16
hash = mul32(hash, 0x85ebca6b)
hash ^= hash >>> 13
hash = mul32(hash, 0xc2b2ae35)
hash ^= hash >>> 16
// Convert to string, ensuring start with character
return 'x' + (hash >>> 0).toString(36)
}
// Multiply two 32-bit numbers
function mul32(m: number, n: number) {
const nLow = n & 0xffff
const nHigh = n - nLow
return (((nHigh * m) | 0) + ((nLow * m) | 0)) | 0
}