Module: Lucerna::Gates::Hashing
- Defined in:
- lib/lucerna/gates/hashing.rb
Overview
The frozen bucketing contract (never change this):
bucket = murmur3_x86_32(utf8("#{salt}:#{unit_id}"), seed 0) % 10000
Buckets are basis points in [0, 10000). Salts are stored in the runtime, never derived from keys. Every SDK in every language must reproduce these values bit-for-bit — the golden vectors in sdks/core/src/vectors.json enforce it.
Constant Summary collapse
- MASK =
0xFFFFFFFF- C1 =
0xcc9e2d51- C2 =
0x1b873593
Class Method Summary collapse
-
.bucket(salt, unit_id) ⇒ Object
The unit's bucket for a salt, in basis points [0, 10000).
-
.murmur3(input, seed = 0) ⇒ Object
MurmurHash3 x86 32-bit over the UTF-8 bytes of
input, as uint32.
Class Method Details
.bucket(salt, unit_id) ⇒ Object
The unit's bucket for a salt, in basis points [0, 10000).
66 67 68 |
# File 'lib/lucerna/gates/hashing.rb', line 66 def bucket(salt, unit_id) murmur3("#{salt}:#{unit_id}") % 10_000 end |
.murmur3(input, seed = 0) ⇒ Object
MurmurHash3 x86 32-bit over the UTF-8 bytes of input, as uint32.
32-bit wraparound is emulated by masking after every multiply/add;
Ruby's right shift on the masked non-negative value matches the
reference implementation's unsigned shift.
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 |
# File 'lib/lucerna/gates/hashing.rb', line 24 def murmur3(input, seed = 0) data = input.encode(Encoding::UTF_8).bytes length = data.length h1 = seed & MASK block_end = length - (length % 4) index = 0 while index < block_end k1 = data[index] | (data[index + 1] << 8) | (data[index + 2] << 16) | (data[index + 3] << 24) k1 = (k1 * C1) & MASK k1 = ((k1 << 15) | (k1 >> 17)) & MASK k1 = (k1 * C2) & MASK h1 ^= k1 h1 = ((h1 << 13) | (h1 >> 19)) & MASK h1 = ((h1 * 5) + 0xe6546b64) & MASK index += 4 end k1 = 0 remaining = length % 4 k1 ^= data[index + 2] << 16 if remaining == 3 k1 ^= data[index + 1] << 8 if remaining >= 2 if remaining >= 1 k1 ^= data[index] k1 = (k1 * C1) & MASK k1 = ((k1 << 15) | (k1 >> 17)) & MASK k1 = (k1 * C2) & MASK h1 ^= k1 end h1 ^= length h1 ^= h1 >> 16 h1 = (h1 * 0x85ebca6b) & MASK h1 ^= h1 >> 13 h1 = (h1 * 0xc2b2ae35) & MASK h1 ^ (h1 >> 16) end |