siphash.odin 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. package siphash
  2. /*
  3. Copyright 2022 zhibog
  4. Made available under the BSD-3 license.
  5. List of contributors:
  6. zhibog: Initial implementation.
  7. Implementation of the SipHash hashing algorithm, as defined at <https://github.com/veorq/SipHash> and <https://www.aumasson.jp/siphash/siphash.pdf>
  8. Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4
  9. */
  10. import "core:crypto"
  11. import "core:encoding/endian"
  12. import "core:math/bits"
  13. /*
  14. High level API
  15. */
  16. KEY_SIZE :: 16
  17. DIGEST_SIZE :: 8
  18. // sum_string_1_3 will hash the given message with the key and return
  19. // the computed hash as a u64
  20. sum_string_1_3 :: proc(msg, key: string) -> u64 {
  21. return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key))
  22. }
  23. // sum_bytes_1_3 will hash the given message with the key and return
  24. // the computed hash as a u64
  25. sum_bytes_1_3 :: proc(msg, key: []byte) -> u64 {
  26. ctx: Context
  27. hash: u64
  28. init(&ctx, key, 1, 3)
  29. update(&ctx, msg)
  30. final(&ctx, &hash)
  31. return hash
  32. }
  33. // sum_string_to_buffer_1_3 will hash the given message with the key and write
  34. // the computed hash into the provided destination buffer
  35. sum_string_to_buffer_1_3 :: proc(msg, key: string, dst: []byte) {
  36. sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst)
  37. }
  38. // sum_bytes_to_buffer_1_3 will hash the given message with the key and write
  39. // the computed hash into the provided destination buffer
  40. sum_bytes_to_buffer_1_3 :: proc(msg, key, dst: []byte) {
  41. hash := sum_bytes_1_3(msg, key)
  42. _collect_output(dst[:], hash)
  43. }
  44. sum_1_3 :: proc {
  45. sum_string_1_3,
  46. sum_bytes_1_3,
  47. sum_string_to_buffer_1_3,
  48. sum_bytes_to_buffer_1_3,
  49. }
  50. // verify_u64_1_3 will check if the supplied tag matches with the output you
  51. // will get from the provided message and key
  52. verify_u64_1_3 :: proc(tag: u64, msg, key: []byte) -> bool {
  53. return sum_bytes_1_3(msg, key) == tag
  54. }
  55. // verify_bytes will check if the supplied tag matches with the output you
  56. // will get from the provided message and key
  57. verify_bytes_1_3 :: proc(tag, msg, key: []byte) -> bool {
  58. derived_tag: [8]byte
  59. sum_bytes_to_buffer_1_3(msg, key, derived_tag[:])
  60. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  61. }
  62. verify_1_3 :: proc {
  63. verify_bytes_1_3,
  64. verify_u64_1_3,
  65. }
  66. // sum_string_2_4 will hash the given message with the key and return
  67. // the computed hash as a u64
  68. sum_string_2_4 :: proc(msg, key: string) -> u64 {
  69. return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key))
  70. }
  71. // sum_bytes_2_4 will hash the given message with the key and return
  72. // the computed hash as a u64
  73. sum_bytes_2_4 :: proc(msg, key: []byte) -> u64 {
  74. ctx: Context
  75. hash: u64
  76. init(&ctx, key, 2, 4)
  77. update(&ctx, msg)
  78. final(&ctx, &hash)
  79. return hash
  80. }
  81. // sum_string_to_buffer_2_4 will hash the given message with the key and write
  82. // the computed hash into the provided destination buffer
  83. sum_string_to_buffer_2_4 :: proc(msg, key: string, dst: []byte) {
  84. sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst)
  85. }
  86. // sum_bytes_to_buffer_2_4 will hash the given message with the key and write
  87. // the computed hash into the provided destination buffer
  88. sum_bytes_to_buffer_2_4 :: proc(msg, key, dst: []byte) {
  89. hash := sum_bytes_2_4(msg, key)
  90. _collect_output(dst[:], hash)
  91. }
  92. sum_2_4 :: proc {
  93. sum_string_2_4,
  94. sum_bytes_2_4,
  95. sum_string_to_buffer_2_4,
  96. sum_bytes_to_buffer_2_4,
  97. }
  98. sum_string :: sum_string_2_4
  99. sum_bytes :: sum_bytes_2_4
  100. sum_string_to_buffer :: sum_string_to_buffer_2_4
  101. sum_bytes_to_buffer :: sum_bytes_to_buffer_2_4
  102. sum :: proc {
  103. sum_string,
  104. sum_bytes,
  105. sum_string_to_buffer,
  106. sum_bytes_to_buffer,
  107. }
  108. // verify_u64_2_4 will check if the supplied tag matches with the output you
  109. // will get from the provided message and key
  110. verify_u64_2_4 :: proc(tag: u64, msg, key: []byte) -> bool {
  111. return sum_bytes_2_4(msg, key) == tag
  112. }
  113. // verify_bytes will check if the supplied tag matches with the output you
  114. // will get from the provided message and key
  115. verify_bytes_2_4 :: proc(tag, msg, key: []byte) -> bool {
  116. derived_tag: [8]byte
  117. sum_bytes_to_buffer_2_4(msg, key, derived_tag[:])
  118. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  119. }
  120. verify_2_4 :: proc {
  121. verify_bytes_2_4,
  122. verify_u64_2_4,
  123. }
  124. verify_bytes :: verify_bytes_2_4
  125. verify_u64 :: verify_u64_2_4
  126. verify :: proc {
  127. verify_bytes,
  128. verify_u64,
  129. }
  130. // sum_string_4_8 will hash the given message with the key and return
  131. // the computed hash as a u64
  132. sum_string_4_8 :: proc(msg, key: string) -> u64 {
  133. return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key))
  134. }
  135. // sum_bytes_4_8 will hash the given message with the key and return
  136. // the computed hash as a u64
  137. sum_bytes_4_8 :: proc(msg, key: []byte) -> u64 {
  138. ctx: Context
  139. hash: u64
  140. init(&ctx, key, 4, 8)
  141. update(&ctx, msg)
  142. final(&ctx, &hash)
  143. return hash
  144. }
  145. // sum_string_to_buffer_4_8 will hash the given message with the key and write
  146. // the computed hash into the provided destination buffer
  147. sum_string_to_buffer_4_8 :: proc(msg, key: string, dst: []byte) {
  148. sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst)
  149. }
  150. // sum_bytes_to_buffer_4_8 will hash the given message with the key and write
  151. // the computed hash into the provided destination buffer
  152. sum_bytes_to_buffer_4_8 :: proc(msg, key, dst: []byte) {
  153. hash := sum_bytes_4_8(msg, key)
  154. _collect_output(dst[:], hash)
  155. }
  156. sum_4_8 :: proc {
  157. sum_string_4_8,
  158. sum_bytes_4_8,
  159. sum_string_to_buffer_4_8,
  160. sum_bytes_to_buffer_4_8,
  161. }
  162. // verify_u64_4_8 will check if the supplied tag matches with the output you
  163. // will get from the provided message and key
  164. verify_u64_4_8 :: proc(tag: u64, msg, key: []byte) -> bool {
  165. return sum_bytes_4_8(msg, key) == tag
  166. }
  167. // verify_bytes will check if the supplied tag matches with the output you
  168. // will get from the provided message and key
  169. verify_bytes_4_8 :: proc(tag, msg, key: []byte) -> bool {
  170. derived_tag: [8]byte
  171. sum_bytes_to_buffer_4_8(msg, key, derived_tag[:])
  172. return crypto.compare_constant_time(derived_tag[:], tag) == 1
  173. }
  174. verify_4_8 :: proc {
  175. verify_bytes_4_8,
  176. verify_u64_4_8,
  177. }
  178. /*
  179. Low level API
  180. */
  181. init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) {
  182. if len(key) != KEY_SIZE {
  183. panic("crypto/siphash; invalid key size")
  184. }
  185. ctx.c_rounds = c_rounds
  186. ctx.d_rounds = d_rounds
  187. is_valid_setting :=
  188. (ctx.c_rounds == 1 && ctx.d_rounds == 3) ||
  189. (ctx.c_rounds == 2 && ctx.d_rounds == 4) ||
  190. (ctx.c_rounds == 4 && ctx.d_rounds == 8)
  191. if !is_valid_setting {
  192. panic("crypto/siphash: incorrect rounds set up")
  193. }
  194. ctx.k0 = endian.unchecked_get_u64le(key[:8])
  195. ctx.k1 = endian.unchecked_get_u64le(key[8:])
  196. ctx.v0 = 0x736f6d6570736575 ~ ctx.k0
  197. ctx.v1 = 0x646f72616e646f6d ~ ctx.k1
  198. ctx.v2 = 0x6c7967656e657261 ~ ctx.k0
  199. ctx.v3 = 0x7465646279746573 ~ ctx.k1
  200. ctx.last_block = 0
  201. ctx.total_length = 0
  202. ctx.is_initialized = true
  203. }
  204. update :: proc(ctx: ^Context, data: []byte) {
  205. assert(ctx.is_initialized, "crypto/siphash: context is not initialized")
  206. data := data
  207. ctx.total_length += len(data)
  208. if ctx.last_block > 0 {
  209. n := copy(ctx.buf[ctx.last_block:], data)
  210. ctx.last_block += n
  211. if ctx.last_block == BLOCK_SIZE {
  212. block(ctx, ctx.buf[:])
  213. ctx.last_block = 0
  214. }
  215. data = data[n:]
  216. }
  217. if len(data) >= BLOCK_SIZE {
  218. n := len(data) &~ (BLOCK_SIZE - 1)
  219. block(ctx, data[:n])
  220. data = data[n:]
  221. }
  222. if len(data) > 0 {
  223. ctx.last_block = copy(ctx.buf[:], data)
  224. }
  225. }
  226. final :: proc(ctx: ^Context, dst: ^u64) {
  227. assert(ctx.is_initialized, "crypto/siphash: context is not initialized")
  228. tmp: [BLOCK_SIZE]byte
  229. copy(tmp[:], ctx.buf[:ctx.last_block])
  230. tmp[7] = byte(ctx.total_length & 0xff)
  231. block(ctx, tmp[:])
  232. ctx.v2 ~= 0xff
  233. for _ in 0 ..< ctx.d_rounds {
  234. _compress(ctx)
  235. }
  236. dst^ = ctx.v0 ~ ctx.v1 ~ ctx.v2 ~ ctx.v3
  237. reset(ctx)
  238. }
  239. reset :: proc(ctx: ^Context) {
  240. ctx.k0, ctx.k1 = 0, 0
  241. ctx.v0, ctx.v1 = 0, 0
  242. ctx.v2, ctx.v3 = 0, 0
  243. ctx.last_block = 0
  244. ctx.total_length = 0
  245. ctx.c_rounds = 0
  246. ctx.d_rounds = 0
  247. ctx.is_initialized = false
  248. }
  249. BLOCK_SIZE :: 8
  250. Context :: struct {
  251. v0, v1, v2, v3: u64, // State values
  252. k0, k1: u64, // Split key
  253. c_rounds: int, // Number of message rounds
  254. d_rounds: int, // Number of finalization rounds
  255. buf: [BLOCK_SIZE]byte, // Provided data
  256. last_block: int, // Offset from the last block
  257. total_length: int,
  258. is_initialized: bool,
  259. }
  260. @(private)
  261. block :: proc "contextless" (ctx: ^Context, buf: []byte) {
  262. buf := buf
  263. for len(buf) >= BLOCK_SIZE {
  264. m := endian.unchecked_get_u64le(buf)
  265. ctx.v3 ~= m
  266. for _ in 0 ..< ctx.c_rounds {
  267. _compress(ctx)
  268. }
  269. ctx.v0 ~= m
  270. buf = buf[BLOCK_SIZE:]
  271. }
  272. }
  273. @(private)
  274. _get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byte {
  275. return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3))
  276. }
  277. @(private)
  278. _collect_output :: #force_inline proc(dst: []byte, hash: u64) {
  279. if len(dst) < DIGEST_SIZE {
  280. panic("crypto/siphash: invalid tag size")
  281. }
  282. dst[0] = _get_byte(7, hash)
  283. dst[1] = _get_byte(6, hash)
  284. dst[2] = _get_byte(5, hash)
  285. dst[3] = _get_byte(4, hash)
  286. dst[4] = _get_byte(3, hash)
  287. dst[5] = _get_byte(2, hash)
  288. dst[6] = _get_byte(1, hash)
  289. dst[7] = _get_byte(0, hash)
  290. }
  291. @(private)
  292. _compress :: #force_inline proc "contextless" (ctx: ^Context) {
  293. ctx.v0 += ctx.v1
  294. ctx.v1 = bits.rotate_left64(ctx.v1, 13)
  295. ctx.v1 ~= ctx.v0
  296. ctx.v0 = bits.rotate_left64(ctx.v0, 32)
  297. ctx.v2 += ctx.v3
  298. ctx.v3 = bits.rotate_left64(ctx.v3, 16)
  299. ctx.v3 ~= ctx.v2
  300. ctx.v0 += ctx.v3
  301. ctx.v3 = bits.rotate_left64(ctx.v3, 21)
  302. ctx.v3 ~= ctx.v0
  303. ctx.v2 += ctx.v1
  304. ctx.v1 = bits.rotate_left64(ctx.v1, 17)
  305. ctx.v1 ~= ctx.v2
  306. ctx.v2 = bits.rotate_left64(ctx.v2, 32)
  307. }