├── .gitattributes ├── .gitignore ├── CRATES.IO-README.md ├── Cargo.lock ├── Cargo.toml ├── MuseAir-icon-dark.png ├── MuseAir-icon-light.png ├── README.md ├── README.zh-Hans.md ├── benches └── hashes.rs ├── museair.cpp ├── results ├── README.md ├── SMHasher3_MuseAir-128_0.3.log ├── SMHasher3_MuseAir-BFast-128_0.3.log ├── SMHasher3_MuseAir-BFast_0.3.log ├── SMHasher3_MuseAir_0.3.log ├── bench-smallkeys.png └── plot-bench-smallkeys.py ├── rustfmt.toml ├── show-asm ├── Cargo.toml └── lib.rs └── src └── lib.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | museair.cpp -linguist-detectable 2 | results/** -linguist-detectable 3 | results/*.log -diff 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /smhasher* 3 | 4 | # ignore temporary files. 5 | /_* 6 | -------------------------------------------------------------------------------- /CRATES.IO-README.md: -------------------------------------------------------------------------------- 1 | # MuseAir 2 | 3 | [![](https://img.shields.io/crates/v/museair)](https://crates.io/crates/museair) 4 | [![](https://img.shields.io/crates/d/museair)](https://crates.io/crates/museair) 5 | [![](https://img.shields.io/crates/l/museair)](#) 6 | [![](https://img.shields.io/docsrs/museair)](https://docs.rs/museair) 7 | [![](https://img.shields.io/github/stars/eternal-io/museair?style=social)](https://github.com/eternal-io/museair) 8 | 9 | 10 | A fast portable hash algorithm with highest bulk throughput and lowest small key latency (1-32 bytes) among portable hashes listed in SMHasher3, 11 | and made improvements for quality and usability. See [repository](https://github.com/eternal-io/museair) for details. 12 | 13 | It provides two variants: `Standard` (items listed in crate root) and `BFast`. 14 | The former offers better quality and the latter offers better performance. 15 | Both variants offer 64-bit and 128-bit output with essentially the same overhead. 16 | 17 | 18 | ## Usage 19 | 20 | ```rust 21 | let seed: u64 = 42; 22 | 23 | let one_shot = museair::hash_128("K--Aethiax".as_bytes(), seed); 24 | let streamed = { 25 | let mut hasher = museair::Hasher::with_seed(seed); 26 | hasher.write("K--Ae".as_bytes()); 27 | hasher.write("thiax".as_bytes()); 28 | hasher.finish_128() 29 | }; 30 | 31 | assert_eq!(one_shot, streamed); 32 | ``` 33 | 34 | 35 | ## Security 36 | 37 | MuseAir is **NOT** designed for cryptographic security. You shouldn't use this for security purposes, 38 | such as ensuring that files have not been maliciously tampered with. For these use cases, consider SHA-3, Ascon or Blake3. 39 | 40 | Besides, MuseAir-`Standard` is planned to be stable after some time (1.0.0). 41 | Due to its improved quality, it will then be available for the following purposes: 42 | 43 | - Persistent file format 44 | - Communication protocol 45 | - ... 46 | 47 | _Until then, it should only be used for local sessions!_ 48 | 49 | 50 | ## Benchmarks 51 | 52 | | Hash | Digest length | Throughput | 53 | |:------------------ | -------------:| -----------------:| 54 | | MuseAir | 64-bit | 30.5 GiB/s | 55 | | MuseAir-128 | 128-bit | 30.4 GiB/s | 56 | | MuseAir-BFast | 64-bit | **36.4 GiB/s** | 57 | | MuseAir-BFast-128 | 128-bit | **36.3 GiB/s** | 58 | | [rapidhash] | 64-bit | 29.4 GiB/s | 59 | | [wyhash] 4.2 | 64-bit | 28.4 GiB/s | 60 | | wyhash.condom 4.2 | 64-bit | 22.8 GiB/s | 61 | | [komihash] 5.10 | 64-bit | 26.8 GiB/s | 62 | 63 | Bench small keys 64 | 65 | 66 | [rapidhash]: https://crates.io/crates/rapidhash 67 | [wyhash]: https://crates.io/crates/wyhash-final4 68 | [komihash]: https://crates.io/crates/komihash 69 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.8" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.3.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 31 | 32 | [[package]] 33 | name = "bumpalo" 34 | version = "3.16.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 37 | 38 | [[package]] 39 | name = "cast" 40 | version = "0.3.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 43 | 44 | [[package]] 45 | name = "cfg-if" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 49 | 50 | [[package]] 51 | name = "ciborium" 52 | version = "0.2.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 55 | dependencies = [ 56 | "ciborium-io", 57 | "ciborium-ll", 58 | "serde", 59 | ] 60 | 61 | [[package]] 62 | name = "ciborium-io" 63 | version = "0.2.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 66 | 67 | [[package]] 68 | name = "ciborium-ll" 69 | version = "0.2.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 72 | dependencies = [ 73 | "ciborium-io", 74 | "half", 75 | ] 76 | 77 | [[package]] 78 | name = "clap" 79 | version = "4.5.15" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" 82 | dependencies = [ 83 | "clap_builder", 84 | ] 85 | 86 | [[package]] 87 | name = "clap_builder" 88 | version = "4.5.15" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" 91 | dependencies = [ 92 | "anstyle", 93 | "clap_lex", 94 | ] 95 | 96 | [[package]] 97 | name = "clap_lex" 98 | version = "0.7.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 101 | 102 | [[package]] 103 | name = "criterion" 104 | version = "0.5.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 107 | dependencies = [ 108 | "anes", 109 | "cast", 110 | "ciborium", 111 | "clap", 112 | "criterion-plot", 113 | "is-terminal", 114 | "itertools", 115 | "num-traits", 116 | "once_cell", 117 | "oorandom", 118 | "plotters", 119 | "rayon", 120 | "regex", 121 | "serde", 122 | "serde_derive", 123 | "serde_json", 124 | "tinytemplate", 125 | "walkdir", 126 | ] 127 | 128 | [[package]] 129 | name = "criterion-plot" 130 | version = "0.5.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 133 | dependencies = [ 134 | "cast", 135 | "itertools", 136 | ] 137 | 138 | [[package]] 139 | name = "crossbeam-deque" 140 | version = "0.8.5" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 143 | dependencies = [ 144 | "crossbeam-epoch", 145 | "crossbeam-utils", 146 | ] 147 | 148 | [[package]] 149 | name = "crossbeam-epoch" 150 | version = "0.9.18" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 153 | dependencies = [ 154 | "crossbeam-utils", 155 | ] 156 | 157 | [[package]] 158 | name = "crossbeam-utils" 159 | version = "0.8.20" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 162 | 163 | [[package]] 164 | name = "crunchy" 165 | version = "0.2.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 168 | 169 | [[package]] 170 | name = "either" 171 | version = "1.13.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 174 | 175 | [[package]] 176 | name = "half" 177 | version = "2.4.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 180 | dependencies = [ 181 | "cfg-if", 182 | "crunchy", 183 | ] 184 | 185 | [[package]] 186 | name = "hashverify" 187 | version = "0.1.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "0d3e441d5a766e64ca462ea764bc22a6a04a12983a4bcf11f32f59911e56a49d" 190 | 191 | [[package]] 192 | name = "hermit-abi" 193 | version = "0.3.9" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 196 | 197 | [[package]] 198 | name = "is-terminal" 199 | version = "0.4.12" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 202 | dependencies = [ 203 | "hermit-abi", 204 | "libc", 205 | "windows-sys 0.52.0", 206 | ] 207 | 208 | [[package]] 209 | name = "itertools" 210 | version = "0.10.5" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 213 | dependencies = [ 214 | "either", 215 | ] 216 | 217 | [[package]] 218 | name = "itoa" 219 | version = "1.0.11" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 222 | 223 | [[package]] 224 | name = "js-sys" 225 | version = "0.3.70" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 228 | dependencies = [ 229 | "wasm-bindgen", 230 | ] 231 | 232 | [[package]] 233 | name = "komihash" 234 | version = "0.4.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "2833487bd16df1ac2c85454a0a26197022c3f32035523a24441fb726f0707688" 237 | 238 | [[package]] 239 | name = "libc" 240 | version = "0.2.155" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 243 | 244 | [[package]] 245 | name = "log" 246 | version = "0.4.22" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 249 | 250 | [[package]] 251 | name = "memchr" 252 | version = "2.7.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 255 | 256 | [[package]] 257 | name = "museair" 258 | version = "0.3.0" 259 | dependencies = [ 260 | "criterion", 261 | "hashverify", 262 | "komihash", 263 | "rapidhash", 264 | "wyhash", 265 | "wyhash-final4", 266 | ] 267 | 268 | [[package]] 269 | name = "num-traits" 270 | version = "0.2.19" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 273 | dependencies = [ 274 | "autocfg", 275 | ] 276 | 277 | [[package]] 278 | name = "once_cell" 279 | version = "1.19.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 282 | 283 | [[package]] 284 | name = "oorandom" 285 | version = "11.1.4" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" 288 | 289 | [[package]] 290 | name = "plotters" 291 | version = "0.3.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" 294 | dependencies = [ 295 | "num-traits", 296 | "plotters-backend", 297 | "plotters-svg", 298 | "wasm-bindgen", 299 | "web-sys", 300 | ] 301 | 302 | [[package]] 303 | name = "plotters-backend" 304 | version = "0.3.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" 307 | 308 | [[package]] 309 | name = "plotters-svg" 310 | version = "0.3.6" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" 313 | dependencies = [ 314 | "plotters-backend", 315 | ] 316 | 317 | [[package]] 318 | name = "proc-macro2" 319 | version = "1.0.86" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 322 | dependencies = [ 323 | "unicode-ident", 324 | ] 325 | 326 | [[package]] 327 | name = "quote" 328 | version = "1.0.36" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 331 | dependencies = [ 332 | "proc-macro2", 333 | ] 334 | 335 | [[package]] 336 | name = "rand_core" 337 | version = "0.6.4" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 340 | 341 | [[package]] 342 | name = "rapidhash" 343 | version = "1.2.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "02d6ba76b70191f99a2c53bb367c118d5064fa33b6aa6a102fa7751b902c7049" 346 | 347 | [[package]] 348 | name = "rayon" 349 | version = "1.10.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 352 | dependencies = [ 353 | "either", 354 | "rayon-core", 355 | ] 356 | 357 | [[package]] 358 | name = "rayon-core" 359 | version = "1.12.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 362 | dependencies = [ 363 | "crossbeam-deque", 364 | "crossbeam-utils", 365 | ] 366 | 367 | [[package]] 368 | name = "regex" 369 | version = "1.10.6" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 372 | dependencies = [ 373 | "aho-corasick", 374 | "memchr", 375 | "regex-automata", 376 | "regex-syntax", 377 | ] 378 | 379 | [[package]] 380 | name = "regex-automata" 381 | version = "0.4.7" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 384 | dependencies = [ 385 | "aho-corasick", 386 | "memchr", 387 | "regex-syntax", 388 | ] 389 | 390 | [[package]] 391 | name = "regex-syntax" 392 | version = "0.8.4" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 395 | 396 | [[package]] 397 | name = "ryu" 398 | version = "1.0.18" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 401 | 402 | [[package]] 403 | name = "same-file" 404 | version = "1.0.6" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 407 | dependencies = [ 408 | "winapi-util", 409 | ] 410 | 411 | [[package]] 412 | name = "serde" 413 | version = "1.0.207" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" 416 | dependencies = [ 417 | "serde_derive", 418 | ] 419 | 420 | [[package]] 421 | name = "serde_derive" 422 | version = "1.0.207" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" 425 | dependencies = [ 426 | "proc-macro2", 427 | "quote", 428 | "syn", 429 | ] 430 | 431 | [[package]] 432 | name = "serde_json" 433 | version = "1.0.124" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" 436 | dependencies = [ 437 | "itoa", 438 | "memchr", 439 | "ryu", 440 | "serde", 441 | ] 442 | 443 | [[package]] 444 | name = "show-asm" 445 | version = "0.1.0" 446 | dependencies = [ 447 | "museair", 448 | "rapidhash", 449 | ] 450 | 451 | [[package]] 452 | name = "syn" 453 | version = "2.0.74" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" 456 | dependencies = [ 457 | "proc-macro2", 458 | "quote", 459 | "unicode-ident", 460 | ] 461 | 462 | [[package]] 463 | name = "tinytemplate" 464 | version = "1.2.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 467 | dependencies = [ 468 | "serde", 469 | "serde_json", 470 | ] 471 | 472 | [[package]] 473 | name = "unicode-ident" 474 | version = "1.0.12" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 477 | 478 | [[package]] 479 | name = "walkdir" 480 | version = "2.5.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 483 | dependencies = [ 484 | "same-file", 485 | "winapi-util", 486 | ] 487 | 488 | [[package]] 489 | name = "wasm-bindgen" 490 | version = "0.2.93" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 493 | dependencies = [ 494 | "cfg-if", 495 | "once_cell", 496 | "wasm-bindgen-macro", 497 | ] 498 | 499 | [[package]] 500 | name = "wasm-bindgen-backend" 501 | version = "0.2.93" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 504 | dependencies = [ 505 | "bumpalo", 506 | "log", 507 | "once_cell", 508 | "proc-macro2", 509 | "quote", 510 | "syn", 511 | "wasm-bindgen-shared", 512 | ] 513 | 514 | [[package]] 515 | name = "wasm-bindgen-macro" 516 | version = "0.2.93" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 519 | dependencies = [ 520 | "quote", 521 | "wasm-bindgen-macro-support", 522 | ] 523 | 524 | [[package]] 525 | name = "wasm-bindgen-macro-support" 526 | version = "0.2.93" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "syn", 533 | "wasm-bindgen-backend", 534 | "wasm-bindgen-shared", 535 | ] 536 | 537 | [[package]] 538 | name = "wasm-bindgen-shared" 539 | version = "0.2.93" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 542 | 543 | [[package]] 544 | name = "web-sys" 545 | version = "0.3.70" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 548 | dependencies = [ 549 | "js-sys", 550 | "wasm-bindgen", 551 | ] 552 | 553 | [[package]] 554 | name = "winapi-util" 555 | version = "0.1.9" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 558 | dependencies = [ 559 | "windows-sys 0.59.0", 560 | ] 561 | 562 | [[package]] 563 | name = "windows-sys" 564 | version = "0.52.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 567 | dependencies = [ 568 | "windows-targets", 569 | ] 570 | 571 | [[package]] 572 | name = "windows-sys" 573 | version = "0.59.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 576 | dependencies = [ 577 | "windows-targets", 578 | ] 579 | 580 | [[package]] 581 | name = "windows-targets" 582 | version = "0.52.6" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 585 | dependencies = [ 586 | "windows_aarch64_gnullvm", 587 | "windows_aarch64_msvc", 588 | "windows_i686_gnu", 589 | "windows_i686_gnullvm", 590 | "windows_i686_msvc", 591 | "windows_x86_64_gnu", 592 | "windows_x86_64_gnullvm", 593 | "windows_x86_64_msvc", 594 | ] 595 | 596 | [[package]] 597 | name = "windows_aarch64_gnullvm" 598 | version = "0.52.6" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 601 | 602 | [[package]] 603 | name = "windows_aarch64_msvc" 604 | version = "0.52.6" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 607 | 608 | [[package]] 609 | name = "windows_i686_gnu" 610 | version = "0.52.6" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 613 | 614 | [[package]] 615 | name = "windows_i686_gnullvm" 616 | version = "0.52.6" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 619 | 620 | [[package]] 621 | name = "windows_i686_msvc" 622 | version = "0.52.6" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 625 | 626 | [[package]] 627 | name = "windows_x86_64_gnu" 628 | version = "0.52.6" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 631 | 632 | [[package]] 633 | name = "windows_x86_64_gnullvm" 634 | version = "0.52.6" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 637 | 638 | [[package]] 639 | name = "windows_x86_64_msvc" 640 | version = "0.52.6" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 643 | 644 | [[package]] 645 | name = "wyhash" 646 | version = "0.5.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295" 649 | dependencies = [ 650 | "rand_core", 651 | ] 652 | 653 | [[package]] 654 | name = "wyhash-final4" 655 | version = "0.2.2" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "e19276aeff1b4987182db862cc0e75a79023528cd1ec951ea3bd6cddba029c7d" 658 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "museair" 3 | version = "0.3.0" 4 | edition = "2021" 5 | authors = ["K--Aethiax"] 6 | 7 | description = "Fast portable hash algorithm with highest bulk throughput and lowest small key latency (1-32 bytes) among portable hashes listed in SMHasher3, and made improvements for quality and usability." 8 | 9 | documentation = "https://docs.rs/museair" 10 | repository = "https://github.com/eternal-io/museair" 11 | license = "MIT OR Apache-2.0" 12 | readme = "CRATES.IO-README.md" 13 | 14 | keywords = ["hash", "hasher", "museair"] 15 | categories = ["algorithms", "no-std"] 16 | 17 | include = ["src/**"] 18 | 19 | 20 | [dev-dependencies] 21 | hashverify = "0.1.0" 22 | criterion = "0.5.1" 23 | komihash = "0.4.1" 24 | wyhash-final4 = "0.2.2" 25 | wyhash = "0.5.0" 26 | rapidhash = "1.2.0" 27 | 28 | 29 | [workspace] 30 | members = ["show-asm"] 31 | 32 | 33 | [[bench]] 34 | name = "hashes" 35 | harness = false 36 | -------------------------------------------------------------------------------- /MuseAir-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eternal-io/museair/4261b3ce3625d505e027253a9858b1754615d399/MuseAir-icon-dark.png -------------------------------------------------------------------------------- /MuseAir-icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eternal-io/museair/4261b3ce3625d505e027253a9858b1754615d399/MuseAir-icon-light.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MuseAir 2 | 3 | [![](https://img.shields.io/crates/v/museair)](https://crates.io/crates/museair) 4 | [![](https://img.shields.io/crates/d/museair)](https://crates.io/crates/museair) 5 | [![](https://img.shields.io/crates/l/museair)](#) 6 | [![](https://img.shields.io/docsrs/museair)](https://docs.rs/museair) 7 | [![](https://img.shields.io/github/stars/eternal-io/museair?style=social)](https://github.com/eternal-io/museair) 8 | 9 |

English简体中文

10 | 11 | 12 | MuseAir is a fast portable hash algorithm that: 13 | 14 | - has the highest throughput among the portable hashes listed in SMHasher3, also provides good performance on small keys (see _Benchmark_). 15 | - made improvements for quality and usability (see _Algorithm analysis_), [passed](results) the full [SMHasher3] `--extra` tests. 16 | 17 | MuseAir provides two variants: `Standard` (default) and `BFast`. The former offers better quality and the latter offers better performance. 18 | 19 | - Both variants offer 64-bit and 128-bit output with essentially the same overhead. 20 | 21 | MuseAir is **NOT** designed for cryptographic security. You shouldn't use this for security purposes, such as ensuring that files have not been maliciously tampered with. For these use cases, consider SHA-3, Ascon or Blake3. 22 | 23 | Besides, MuseAir-`Standard` is planned to be stable after some time (1.0.0). Due to its improved quality, it will then be available for the following purposes: 24 | 25 | - Persistent file format 26 | - Communication protocol 27 | - ... 28 | 29 | _Until then, it should only be used for local sessions!_ 30 | 31 | 32 | ## Benchmarks 33 | 34 | AMD Ryzen 7 5700G 4.6GHz Desktop, Windows 10 22H2, rustc 1.82.0 (f6e511eec 2024-10-15) 35 | 36 | - SMHasher3 (34093a3 2024-06-17) runs in WSL 2 with clang 14.0.0-1ubuntu1.1 37 | 38 | 39 | ### Bulk datas 40 | 41 | | Hash | Digest length | Throughput (C++ - SMHasher3) | Throughput (Rust - Criterion.rs) | 42 | |:----------------- | -------------:| ----------------------------:| --------------------------------:| 43 | | MuseAir | 64-bit | 28.5 GiB/s | 30.5 GiB/s | 44 | | MuseAir-128 | 128-bit | 28.5 GiB/s | 30.4 GiB/s | 45 | | MuseAir-BFast | 64-bit | 33.3 GiB/s | 36.4 GiB/s | 46 | | MuseAir-BFast-128 | 128-bit | 33.3 GiB/s | 36.3 GiB/s | 47 | | rapidhash | 64-bit | 31.9 GiB/s | 29.4 GiB/s | 48 | | wyhash 4.2 | 64-bit | 31.9 GiB/s | 28.4 GiB/s | 49 | | wyhash.condom 4.2 | 64-bit | 25.3 GiB/s | 22.8 GiB/s | 50 | | komihash 5.7 | 64-bit | 25.5 GiB/s | N/A | 51 | | komihash 5.10 | 64-bit | N/A | 26.8 GiB/s | 52 | 53 | *(These results are obtained by running `./SMHasher3 --test=Speed ` and `cargo bench`)* 54 | 55 | Peak throughput is implementation specific. But no matter what, MuseAir is the fastest for bulk datas among portable hashes, reaching 1.14x the previous fastest (wyhash). 56 | 57 | 58 | ### Small keys 59 | 60 | Bench small keys 61 | 62 | *(These results are obtained by running `./SMHasher3 --test=Speed `)* 63 | 64 | For the more common small keys of 1-32 bytes, MuseAir has significant performance advantages. In this range, MuseAir is still the fastest on average. 65 | 66 | 67 | ## Implementations 68 | 69 | This repository provides the official Rust implementation of MuseAir. You can find this crate on [crates.io](https://crates.io/crates/museair). 70 | 71 | | Language | Link 72 | |:-------- |:---- 73 | | **C** | [eternal-io/museair-c](https://github.com/eternal-io/museair-c) 74 | | **C++** | [Twilight-Dream-Of-Magic/museair-cpp](https://github.com/Twilight-Dream-Of-Magic/museair-cpp) 75 | 76 | 77 | ## Algorithm analysis 78 | 79 | First define `wide_mul` and `fold_mul`: 80 | 81 | ```rust 82 | /// 64 x 64 -> 128 multiplication, returns lower 64-bit, then upper 64-bit. 83 | fn wide_mul(a: u64, b: u64) -> (u64, u64) { 84 | x = a as u128 * b as u128; 85 | (x as u64, (x >> 64) as u64) 86 | } 87 | 88 | /// XOR-fold the lower half and the upper half of the multiplication result. 89 | fn fold_mul(a: u64, b: u64) -> u64 { 90 | let (lo, hi) = wide_mul(a, b); 91 | lo ^ hi 92 | } 93 | ``` 94 | 95 | **For small keys**, the reason why there is a significant speedup for 16-32 bytes is mainly because the _data hazard_ is solved: 96 | 97 | ```rust 98 | /* not what they actually read, just to simplify the situation. */ 99 | 100 | let mut acc_i = read_u64(&bytes[0..8]); 101 | let mut acc_j = read_u64(&bytes[8..16]); 102 | 103 | if bytes.len() > 16 { 104 | let (lo0, hi0) = wide_mul(CONSTANT[2], CONSTANT[3] ^ read_u64(&bytes[16..24])); 105 | let (lo1, hi1) = wide_mul(CONSTANT[4], CONSTANT[5] ^ read_u64(&bytes[24..32])); 106 | acc_i ^= lo0 ^ hi1; 107 | acc_j ^= lo1 ^ hi0; 108 | } 109 | ``` 110 | 111 | **For bulk datas**, consider wyhash's core loop: 112 | 113 | ```rust 114 | acc0 = fold_mul(acc0 ^ read_u64(&bytes[8 * 0..]), SECRET[0] ^ read_u64(&bytes[8 * 1..])); 115 | acc1 = fold_mul(acc1 ^ read_u64(&bytes[8 * 2..]), SECRET[1] ^ read_u64(&bytes[8 * 3..])); 116 | acc2 = fold_mul(acc2 ^ read_u64(&bytes[8 * 4..]), SECRET[2] ^ read_u64(&bytes[8 * 5..])); 117 | /* Left side */ /* Right side */ 118 | ``` 119 | 120 | actually the following problems: 121 | 122 | 1. Divide the input into multiple strips and process them separately, without diffusion between strips. 123 | 2. Direct folding after wide multiplication. Although it is beneficial to confusion and further diffusion, it will also cause a certain _entropy loss_, and possible to design collisions accordingly. 124 | 3. Once the _right side_ input happens to be the same as `SECRET[n]`, it causes one of the multipliers to be zero. Due to the nature of multiplication, the accumulator of the current strip will be destructively zeroized, and all past states will no longer exist. This situation is called "blinding multiplication" —— It is actually very easy to design collisions here, and its security comes entirely from the confidentiality of `SECRET[..]`, a set of constants that are independent of the seed. Therefore this type of attack is also called "seed-independent attack". This limits its application in communication protocols, persistent file formats, etc. 125 | 126 | In fact, to alleviate problem 3, wyhash also proposed the `condom` mode, uses modified folding multiplication: 127 | 128 | ```rust 129 | fn fold_mul(a: u64, b: u64) -> u64 { 130 | let (lo, hi) = wide_mul(a, b); 131 | a ^ b ^ lo ^ hi 132 | } 133 | ``` 134 | 135 | can obviously avoid this problem now. However, when the _right side_ continues to be zero, the _left side_ inputs will not diffuse at all and will be repeatedly overwritten by subsequent inputs. Worse, performance dropped by more than 20%. At this time, there are other algorithms that are better than it, such as [komihash]. 136 | 137 | In order to solve all the above problems, MuseAir proposed the **Ring Accumulator Group** structure: 138 | 139 | ```rust 140 | /* `wrapping_add` omitted. */ 141 | 142 | state[0] ^= read_u64(&bytes[8 * 0..]); 143 | state[1] ^= read_u64(&bytes[8 * 1..]); 144 | let (lo0, hi0) = wide_mul(state[0], state[1]); 145 | state[0] += ring_prev ^ hi0; 146 | 147 | state[1] ^= read_u64(&bytes[8 * 2..]); 148 | state[2] ^= read_u64(&bytes[8 * 3..]); 149 | let (lo1, hi1) = wide_mul(state[1], state[2]); 150 | state[1] += lo0 ^ hi1; 151 | 152 | ... 153 | 154 | state[5] ^= read_u64(&bytes[8 * 10..]); 155 | state[0] ^= read_u64(&bytes[8 * 11..]); 156 | let (lo5, hi5) = wide_mul(state[5], state[0]); 157 | state[5] += lo4 ^ hi5; 158 | 159 | ring_prev = lo5; 160 | ``` 161 | 162 | This is the accumulator group for the `Standard` variant. For the `BFast` variant, simply replace `+=` with `=`. 163 | 164 | For problem 1 and 2, all accumulator updates come from _this_ upper 64-bit and _previous_ lower 64-bit, which has good diffusion properties. 165 | 166 | For problem 3, since the multipliers are always dynamic and thanks to good diffusion, MuseAir is not affected by seed-independent attacks. As for blinding multiplication, the `Standard` variant does not overwrite the accumulators, is therefore not affected by this. The `BFast` variant overwrites the accumulators, which needs a brief discussion: 167 | 168 | - After a certain read, if `state[0] == 0 && state[1] != 0`, then overwrite the accumulator will not cause any data loss. Also, `state[0]` will almost never fall into an all-zero state due to the lagging mix-in of the multiplication results. 169 | - After a certain read, if `state[0] != 0 && state[1] == 0`, then overwrite the accumulator will cause the data of `state[0]` (the most recent 8 bytes) being lost. As for the previous datas, they have already been diffused into the entire state and is not affected. Likewise, `state[0]` will almost never fall into the all-zero state, but for `state[1]`: 170 | - If the next read unfortunately encounters an all-zero block, or the first byte is `0x01`, and the last seven bytes are all zeros (for universal inputs, only $2^{-127}$ probability go there), then it will remain all-zero for this round. 171 | - Of course it is more likely to hit a non-all-zero block. The precious upper 64-bit will bring it back from the low entropy state immediately after the next multiplication. 172 | 173 | In summary, for universal inputs, MuseAir-BFast only has a $2^{-64}$ probability that a certain 8 bytes of input will not affect the output. Referring to wyhash, there is a $2^{-63}$ probability that the past one-third of input does not affect the output. 174 | 175 | As for performance, its improvement mainly comes from a deep understanding of instruction-level parallelism (ILP). Benchmarks show that the performance difference between MuseAir-Standard and wyhash is less than 6%. 176 | 177 | MuseAir-Standard will be the fastest portable hash available for communication protocols and persistent file formats. 178 | 179 | 180 | ## License 181 | 182 | MuseAir hash algorithm itself and its reference implementation `museair.cpp` are released into the public domain under the CC0 1.0 license. 183 | 184 | The all other codes in this repository are released under the MIT or Apache 2.0 dual license, at your option. 185 | 186 | 187 | [komihash]: https://github.com/avaneev/komihash 188 | [SMHasher3]: https://gitlab.com/fwojcik/smhasher3 189 | -------------------------------------------------------------------------------- /README.zh-Hans.md: -------------------------------------------------------------------------------- 1 | # MuseAir 2 | 3 | [![](https://img.shields.io/crates/v/museair)](https://crates.io/crates/museair) 4 | [![](https://img.shields.io/crates/d/museair)](https://crates.io/crates/museair) 5 | [![](https://img.shields.io/crates/l/museair)](#) 6 | [![](https://img.shields.io/docsrs/museair)](https://docs.rs/museair) 7 | [![](https://img.shields.io/github/stars/eternal-io/museair?style=social)](https://github.com/eternal-io/museair) 8 | 9 |

简体中文English

10 | 11 | 12 | MuseAir 是一个快速便携(portable)散列算法,拥有便携散列中最高的吞吐量,并且在短键上也能提供不俗的表现(详见“*基准测试*”)。她还针对质量和可用性进行了改进(详见“*算法分析*”),并且能够[*通过*](results)完整的 [SMHasher3] 扩展测试。 13 | 14 | MuseAir 提供了两种变体:`Standard`(默认)和 `BFast`。前者提供更好的质量,后者提供更好的性能。 15 | 16 | - 两种变体都提供 64 位和 128 位输出,并且开销基本一致。 17 | 18 | MuseAir **不是**为了密码学安全而设计的,你不应将其用于安全用途,例如,保证文件未经恶意篡改。对于这类用途,请考虑 SHA-3,Ascon 或 Blake3。 19 | 20 | 此外,MuseAir-`Standard` 计划在一段时间(1.0.0)之后稳定。由于她已提升的质量,届时,她将可以用于以下用途: 21 | 22 | - 持久文件格式 23 | - 通信协议 24 | - …… 25 | 26 | 在此之前,她应该只用于本地会话。 27 | 28 | 29 | ## 基准测试 / Benchmarks 30 | 31 | AMD Ryzen 7 5700G 4.6GHz Desktop, Windows 10 22H2, rustc 1.82.0 (f6e511eec 2024-10-15) 32 | 33 | - SMHasher3 (34093a3 2024-06-17) runs in WSL 2 with clang 14.0.0-1ubuntu1.1 34 | 35 | 36 | ### 大块数据 / Bulk datas 37 | 38 | | Hash | Digest length | Throughput (C++ - SMHasher3) | Throughput (Rust - Criterion.rs) | 39 | |:----------------- | -------------:| ----------------------------:| --------------------------------:| 40 | | MuseAir | 64-bit | 28.5 GiB/s | 30.5 GiB/s | 41 | | MuseAir-128 | 128-bit | 28.5 GiB/s | 30.4 GiB/s | 42 | | MuseAir-BFast | 64-bit | 33.3 GiB/s | 36.4 GiB/s | 43 | | MuseAir-BFast-128 | 128-bit | 33.3 GiB/s | 36.3 GiB/s | 44 | | rapidhash | 64-bit | 31.9 GiB/s | 29.4 GiB/s | 45 | | wyhash 4.2 | 64-bit | 31.9 GiB/s | 28.4 GiB/s | 46 | | wyhash.condom 4.2 | 64-bit | 25.3 GiB/s | 22.8 GiB/s | 47 | | komihash 5.7 | 64-bit | 25.5 GiB/s | N/A | 48 | | komihash 5.10 | 64-bit | N/A | 26.8 GiB/s | 49 | 50 | *(These results are obtained by running `./SMHasher3 --test=Speed ` and `cargo bench`)* 51 | 52 | 峰值吞吐量与具体实现有关。但不论如何,对于大块数据,MuseAir 都是便携散列当中最快的,可以达到先前最快(wyhash)的 1.14 倍。 53 | 54 | 55 | ### 短键 / Small keys 56 | 57 | Bench small keys 58 | 59 | *(These results are obtained by running `./SMHasher3 --test=Speed `)* 60 | 61 | 对于更加常见的 1-32 bytes 短键,MuseAir 系列算法拥有显著的性能优势。在这个范围内,平均而言,她仍是最快的。 62 | 63 | 64 | ## 实现 / Implementations 65 | 66 | 这个存储库提供 MuseAir 的官方 Rust 实现,你可以在 [crates.io](https://crates.io/crates/museair) 上找到这个 crate。 67 | 68 | | Language | Link 69 | |:-------- |:---- 70 | | **C** | [eternal-io/museair-c](https://github.com/eternal-io/museair-c) 71 | | **C++** | [Twilight-Dream-Of-Magic/museair-cpp](https://github.com/Twilight-Dream-Of-Magic/museair-cpp) 72 | 73 | 74 | ## 算法分析 / Algorithm analysis 75 | 76 | 首先定义 `wide_mul` 和 `fold_mul`: 77 | 78 | ```rust 79 | /// 64 x 64 -> 128 multiplication, returns lower 64-bit, then upper 64-bit. 80 | fn wide_mul(a: u64, b: u64) -> (u64, u64) { 81 | x = a as u128 * b as u128; 82 | (x as u64, (x >> 64) as u64) 83 | } 84 | 85 | /// XOR-fold the lower half and the upper half of the multiplication result. 86 | fn fold_mul(a: u64, b: u64) -> u64 { 87 | let (lo, hi) = wide_mul(a, b); 88 | lo ^ hi 89 | } 90 | ``` 91 | 92 | **对于短键**,之所以对 16-32 字节长度有显著提速,主要是因为解决了数据依赖问题,使得那部分的乘法运算不需要等待先前数据,有效地利用了 CPU 流水线: 93 | 94 | ```rust 95 | /* not what they actually read, just to simplify the situation. */ 96 | 97 | let mut acc_i = read_u64(&bytes[0..8]); 98 | let mut acc_j = read_u64(&bytes[8..16]); 99 | 100 | if bytes.len() > 16 { 101 | let (lo0, hi0) = wide_mul(CONSTANT[2], CONSTANT[3] ^ read_u64(&bytes[16..24])); 102 | let (lo1, hi1) = wide_mul(CONSTANT[4], CONSTANT[5] ^ read_u64(&bytes[24..32])); 103 | acc_i ^= lo0 ^ hi1; 104 | acc_j ^= lo1 ^ hi0; 105 | } 106 | ``` 107 | 108 | **对于大块数据**,考虑 wyhash 的核心循环: 109 | 110 | ```rust 111 | acc0 = fold_mul(acc0 ^ read_u64(&bytes[8 * 0..]), SECRET[0] ^ read_u64(&bytes[8 * 1..])); 112 | acc1 = fold_mul(acc1 ^ read_u64(&bytes[8 * 2..]), SECRET[1] ^ read_u64(&bytes[8 * 3..])); 113 | acc2 = fold_mul(acc2 ^ read_u64(&bytes[8 * 4..]), SECRET[2] ^ read_u64(&bytes[8 * 5..])); 114 | /* Left side */ /* Right side */ 115 | ``` 116 | 117 | 实际上有以下问题: 118 | 119 | 1. 将输入划分为多个条带分别处理,条带之间没有扩散(diffusion)。 120 | 2. 宽乘法后直接折叠,尽管有利于混淆(confusion)和进一步扩散,但也会造成一定的*熵损失*,有可能依此设计出碰撞。 121 | 3. 当*右侧*输入恰好与 `SECRET[n]` 相同时,会导致其中一个乘数为零。由于乘法的性质“零乘以任何数都等于零”,当前条带的累加器将被毁灭性清零,过去的所有状态都将不复存在。这一情形也被称作“致盲乘法”——在这里,设计碰撞实际上非常容易,它的安全性完全来自于 `SECRET[..]` 的保密,一组与种子(seed)无关的常数。因此这类攻击也被称作“种子无关攻击”。这限制了它在通信协议、持久文件格式等方面的应用。 122 | 123 | 实际上,为了缓解问题 3,wyhash 还提出了 `condom` 模式,使用修改的折叠乘法: 124 | 125 | ```rust 126 | fn fold_mul(a: u64, b: u64) -> u64 { 127 | let (lo, hi) = wide_mul(a, b); 128 | a ^ b ^ lo ^ hi 129 | } 130 | ``` 131 | 132 | 显然能够避免致盲乘法问题。但当*右侧*持续为零时,*左侧*的输入将完全不会扩散,还将被后来的输入反复覆盖。此外,还有超过 20% 的性能下降。此时已有其它算法比它快且好了,比如 [komihash]。 133 | 134 | 为了解决上述所有问题,MuseAir 提出了**环形累加器组**结构: 135 | 136 | ```rust 137 | /* `wrapping_add` omitted. */ 138 | 139 | state[0] ^= read_u64(&bytes[8 * 0..]); 140 | state[1] ^= read_u64(&bytes[8 * 1..]); 141 | let (lo0, hi0) = wide_mul(state[0], state[1]); 142 | state[0] += ring_prev ^ hi0; 143 | 144 | state[1] ^= read_u64(&bytes[8 * 2..]); 145 | state[2] ^= read_u64(&bytes[8 * 3..]); 146 | let (lo1, hi1) = wide_mul(state[1], state[2]); 147 | state[1] += lo0 ^ hi1; 148 | 149 | ... 150 | 151 | state[5] ^= read_u64(&bytes[8 * 10..]); 152 | state[0] ^= read_u64(&bytes[8 * 11..]); 153 | let (lo5, hi5) = wide_mul(state[5], state[0]); 154 | state[5] += lo4 ^ hi5; 155 | 156 | ring_prev = lo5; 157 | ``` 158 | 159 | 这是 `Standard` 变体的累加器组。对于 `BFast` 变体,直接将 `+=` 替换成 `=` 即是。 160 | 161 | 对于问题 1 和 2:所有累加器的更新皆来自于本次乘法的高 64 位结果和上次乘法的低 64 位结果,拥有良好的扩散性质。 162 | 163 | 对于问题 3:由于乘数总是动态的,且得益于良好的扩散,MuseAir 不会遭受种子无关攻击。至于致盲乘法,`Standard` 变体没有对累加器的覆写,因此不受此影响。`BFast` 变体有对累加器的覆写,需要进行简单讨论: 164 | 165 | - 若在某次读入之后,`state[0] == 0 && state[1] != 0`,则接下来覆写累加器时,不会导致任何数据丢失。同时,由于乘法结果的滞后混入,`state[0]` 几乎不会陷入全零状态。 166 | - 若在某次读入之后,`state[0] != 0 && state[1] == 0`,则接下来覆写累加器时,会导致读入`state[0]`的那部分数据(8 字节)丢失。至于更先前的数据,则早已被扩散至整个状态中,不受影响。同样,`state[0]` 几乎不会陷入全零状态,但对于`state[1]`: 167 | - 如果接下来的读取不幸碰上了全零块,或是前七个字节都是零,最后一个字节是 `0x01`(对于普遍输入而言,只有 $2^{-127}$ 概率能走到这里),那么它在这一轮内都会保持全零。 168 | - 当然,它更有可能碰上非全零块。在接下来的乘法完成之后,拥有宝贵混合的高 64 位乘法结果还会立刻让它从低熵状态中恢复。 169 | 170 | 综上,对于普遍输入,MuseAir-BFast 只有 $2^{-64}$ 概率导致某 8 个字节的输入不影响输出。参考 wyhash,有 $2^{-63}$ 概率导致过去三分之一的输入不影响输出。 171 | 172 | 至于性能,它的提升主要来自对指令级并行(ILP)的深入理解。基准测试表明 MuseAir-Standard 与 wyhash 的性能差异在 6% 以下。 173 | 174 | MuseAir-Standard 将是能够用于通信协议/持久文件格式的最快的便携散列。 175 | 176 | _扩展资料:MuseAir 0.2 算法介绍,[B站专栏](https://www.bilibili.com/read/cv37413023) 或 [知乎文章](https://zhuanlan.zhihu.com/p/715753300)。尽管介绍的是老版本,但其中有一些未在此处提及的设计动机,没有太大变动,仍具有一定参考性。_ 177 | 178 | 179 | ## 许可 / License 180 | 181 | MuseAir 散列算法本身及其参考实现 `museair.cpp` 以 CC0 1.0 许可发布到公共领域。 182 | 183 | 除此之外,该存储库下的其它所有代码以 MIT 和 Apache 2.0 双许可发布。 184 | 185 | 186 | [komihash]: https://github.com/avaneev/komihash 187 | [SMHasher3]: https://gitlab.com/fwojcik/smhasher3 188 | -------------------------------------------------------------------------------- /benches/hashes.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; 2 | use wyhash_final4::{generics::WyHashVariant, WyHash64, WyHash64Condom}; 3 | 4 | #[rustfmt::skip] 5 | fn criterion_benchmark(c: &mut Criterion) { 6 | const SIZE: u64 = 256 * 1024; 7 | let msg = vec![0xABu8; SIZE as usize]; 8 | 9 | c.benchmark_group("MuseAir") 10 | .throughput(Throughput::Bytes(SIZE)) 11 | .bench_function("museair", |b| { 12 | b.iter(|| { 13 | black_box(museair::hash(&msg, 42)); 14 | }) 15 | }) 16 | .bench_function("museair-128", |b| { 17 | b.iter(|| { 18 | black_box(museair::hash_128(&msg, 42)); 19 | }) 20 | }) 21 | .bench_function("museair-bfast", |b| { 22 | b.iter(|| { 23 | black_box(museair::bfast::hash(&msg, 42)); 24 | }) 25 | }) 26 | .bench_function("museair-bfast-128", |b| { 27 | b.iter(|| { 28 | black_box(museair::bfast::hash_128(&msg, 42)); 29 | }) 30 | }) 31 | ; 32 | 33 | c.benchmark_group("wyhash") 34 | .throughput(Throughput::Bytes(SIZE)) 35 | .bench_function("wyhash-final4", |b| { 36 | b.iter(|| { 37 | black_box(WyHash64::with_seed(42).hash(&msg)); 38 | }) 39 | }) 40 | .bench_function("wyhash-final4.strict", |b| { 41 | b.iter(|| { 42 | black_box(WyHash64Condom::with_seed(42).hash(&msg)); 43 | }) 44 | }) 45 | ; 46 | 47 | c.benchmark_group("rapidhash") 48 | .throughput(Throughput::Bytes(SIZE)) 49 | .bench_function("rapidhash", |b| { 50 | b.iter(|| { 51 | black_box(rapidhash::rapidhash_seeded(&msg, 42)); // faster than `rapidhash_inline` that force inlined. 52 | }) 53 | }) 54 | ; 55 | 56 | c.benchmark_group("komihash") 57 | .throughput(Throughput::Bytes(SIZE)) 58 | .bench_function("komihash", |b| { 59 | b.iter(|| { 60 | black_box(komihash::komihash(&msg, 42)); 61 | }) 62 | }) 63 | ; 64 | } 65 | 66 | criterion_group! { 67 | name=benches; 68 | config=Criterion::default(); 69 | targets=criterion_benchmark 70 | } 71 | criterion_main!(benches); 72 | -------------------------------------------------------------------------------- /museair.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MuseAir hash algorithm itself and its reference implementation (this file) 3 | * by K--Aethiax are released into the public domain under the CC0 1.0 license. 4 | * To view a copy of this license, visit: https://creativecommons.org/publicdomain/zero/1.0/ 5 | */ 6 | 7 | #include "Platform.h" 8 | #include "Hashlib.h" 9 | 10 | #include "Mathmult.h" 11 | 12 | #define MUSEAIR_ALGORITHM_VERSION "0.3" 13 | 14 | #define u64x(N) N * 8 15 | 16 | // `AiryAi(0)` mantissa calculated by Y-Cruncher. 17 | static const uint64_t MUSEAIR_CONSTANT[7] = { 18 | UINT64_C(0x5ae31e589c56e17a), UINT64_C(0x96d7bb04e64f6da9), UINT64_C(0x7ab1006b26f9eb64), 19 | UINT64_C(0x21233394220b8457), UINT64_C(0x047cb9557c9f3b43), UINT64_C(0xd24f2590c0bcee28), 20 | UINT64_C(0x33ea8f71bb6016d8), 21 | }; 22 | 23 | //------------------------------------------------------------------------------ 24 | 25 | template 26 | static FORCE_INLINE uint64_t museair_read_u64(const uint8_t* p) { 27 | return GET_U64(p, 0); 28 | } 29 | template 30 | static FORCE_INLINE uint64_t museair_read_u32(const uint8_t* p) { 31 | return (uint64_t)GET_U32(p, 0); 32 | } 33 | template 34 | static FORCE_INLINE void museair_read_short(const uint8_t* bytes, const size_t len, uint64_t* i, uint64_t* j) { 35 | if (len >= 4) { 36 | int off = (len & 24) >> (len >> 3); // len >= 8 ? 4 : 0 37 | *i = (museair_read_u32(bytes) << 32) | museair_read_u32(bytes + len - 4); 38 | *j = (museair_read_u32(bytes + off) << 32) | museair_read_u32(bytes + len - 4 - off); 39 | } else if (len > 0) { 40 | // MSB <-> LSB 41 | // [0] [0] [0] for len == 1 (0b01) 42 | // [0] [1] [1] for len == 2 (0b10) 43 | // [0] [1] [2] for len == 3 (0b11) 44 | *i = ((uint64_t)bytes[0] << 48) | ((uint64_t)bytes[len >> 1] << 24) | (uint64_t)bytes[len - 1]; 45 | *j = 0; 46 | } else { 47 | *i = 0; 48 | *j = 0; 49 | } 50 | } 51 | 52 | template 53 | static FORCE_INLINE void museair_mumix(uint64_t* state_p, uint64_t* state_q, uint64_t input_p, uint64_t input_q) { 54 | if (!bfast) { 55 | uint64_t lo, hi; 56 | *state_p ^= input_p; 57 | *state_q ^= input_q; 58 | MathMult::mult64_128(lo, hi, *state_p, *state_q); 59 | *state_p ^= lo; 60 | *state_q ^= hi; 61 | } else { 62 | MathMult::mult64_128(*state_p, *state_q, *state_p ^ input_p, *state_q ^ input_q); 63 | } 64 | } 65 | 66 | //------------------------------------------------------------------------------ 67 | 68 | template 69 | static FORCE_INLINE void museair_hash_short(const uint8_t* bytes, 70 | const size_t len, 71 | const seed_t seed, 72 | uint64_t* out_lo, 73 | uint64_t* out_hi); 74 | 75 | template 76 | static NEVER_INLINE void museair_hash_loong(const uint8_t* bytes, 77 | const size_t len, 78 | const seed_t seed, 79 | uint64_t* out_lo, 80 | uint64_t* out_hi); 81 | 82 | template 83 | static inline void MuseAirHash(const void* bytes, const size_t len, const seed_t seed, void* out) { 84 | uint64_t out_lo, out_hi; 85 | 86 | if (likely(len <= u64x(4))) { 87 | museair_hash_short((const uint8_t*)bytes, len, seed, &out_lo, &out_hi); 88 | } else { 89 | museair_hash_loong((const uint8_t*)bytes, len, seed, &out_lo, &out_hi); 90 | } 91 | 92 | if (b128) { 93 | if (isLE()) { 94 | PUT_U64(out_lo, (uint8_t*)out, 0); 95 | PUT_U64(out_hi, (uint8_t*)out, 8); 96 | } else { 97 | PUT_U64(out_lo, (uint8_t*)out, 0); 98 | PUT_U64(out_hi, (uint8_t*)out, 8); 99 | } 100 | } else { 101 | if (isLE()) { 102 | PUT_U64(out_lo, (uint8_t*)out, 0); 103 | } else { 104 | PUT_U64(out_lo, (uint8_t*)out, 0); 105 | } 106 | } 107 | } 108 | 109 | template 110 | static FORCE_INLINE void museair_hash_short(const uint8_t* bytes, 111 | const size_t len, 112 | const seed_t seed, 113 | uint64_t* out_lo, 114 | uint64_t* out_hi) { 115 | uint64_t lo0, lo1, lo2; 116 | uint64_t hi0, hi1, hi2; 117 | 118 | MathMult::mult64_128(lo2, hi2, seed ^ MUSEAIR_CONSTANT[0], len ^ MUSEAIR_CONSTANT[1]); 119 | 120 | uint64_t i, j; 121 | museair_read_short(bytes, len <= 16 ? len : 16, &i, &j); 122 | i ^= len ^ lo2; 123 | j ^= seed ^ hi2; 124 | 125 | if (unlikely(len > u64x(2))) { 126 | uint64_t u, v; 127 | museair_read_short(bytes + u64x(2), len - u64x(2), &u, &v); 128 | MathMult::mult64_128(lo0, hi0, MUSEAIR_CONSTANT[2], MUSEAIR_CONSTANT[3] ^ u); 129 | MathMult::mult64_128(lo1, hi1, MUSEAIR_CONSTANT[4], MUSEAIR_CONSTANT[5] ^ v); 130 | i ^= lo0 ^ hi1; 131 | j ^= lo1 ^ hi0; 132 | } 133 | 134 | if (b128) { 135 | MathMult::mult64_128(lo0, hi0, i, j); 136 | MathMult::mult64_128(lo1, hi1, i ^ MUSEAIR_CONSTANT[2], j ^ MUSEAIR_CONSTANT[3]); 137 | i = lo0 ^ hi1; 138 | j = lo1 ^ hi0; 139 | MathMult::mult64_128(lo0, hi0, i, j); 140 | MathMult::mult64_128(lo1, hi1, i ^ MUSEAIR_CONSTANT[4], j ^ MUSEAIR_CONSTANT[5]); 141 | *out_lo = lo0 ^ hi1; 142 | *out_hi = lo1 ^ hi0; 143 | } else { 144 | MathMult::mult64_128(lo2, hi2, i ^ MUSEAIR_CONSTANT[2], j ^ MUSEAIR_CONSTANT[3]); 145 | if (!bfast) { 146 | i ^= lo2; 147 | j ^= hi2; 148 | } else { 149 | i = lo2; 150 | j = hi2; 151 | } 152 | MathMult::mult64_128(lo2, hi2, i ^ MUSEAIR_CONSTANT[4], j ^ MUSEAIR_CONSTANT[5]); 153 | if (!bfast) { 154 | *out_lo = i ^ j ^ lo2 ^ hi2; 155 | } else { 156 | *out_lo = lo2 ^ hi2; 157 | } 158 | } 159 | } 160 | 161 | template 162 | static NEVER_INLINE void museair_hash_loong(const uint8_t* bytes, 163 | const size_t len, 164 | const seed_t seed, 165 | uint64_t* out_lo, 166 | uint64_t* out_hi) { 167 | const uint8_t* p = bytes; 168 | size_t q = len; 169 | 170 | uint64_t i, j, k; 171 | 172 | uint64_t lo0, lo1, lo2, lo3, lo4, lo5 = MUSEAIR_CONSTANT[6]; 173 | uint64_t hi0, hi1, hi2, hi3, hi4, hi5; 174 | 175 | uint64_t state[6] = {MUSEAIR_CONSTANT[0] + seed, MUSEAIR_CONSTANT[1] - seed, MUSEAIR_CONSTANT[2] ^ seed, 176 | MUSEAIR_CONSTANT[3] + seed, MUSEAIR_CONSTANT[4] - seed, MUSEAIR_CONSTANT[5] ^ seed}; 177 | 178 | if (unlikely(q >= u64x(12))) { 179 | do { 180 | if (!bfast) { 181 | state[0] ^= museair_read_u64(p + u64x(0)); 182 | state[1] ^= museair_read_u64(p + u64x(1)); 183 | MathMult::mult64_128(lo0, hi0, state[0], state[1]); 184 | state[0] += lo5 ^ hi0; 185 | 186 | state[1] ^= museair_read_u64(p + u64x(2)); 187 | state[2] ^= museair_read_u64(p + u64x(3)); 188 | MathMult::mult64_128(lo1, hi1, state[1], state[2]); 189 | state[1] += lo0 ^ hi1; 190 | 191 | state[2] ^= museair_read_u64(p + u64x(4)); 192 | state[3] ^= museair_read_u64(p + u64x(5)); 193 | MathMult::mult64_128(lo2, hi2, state[2], state[3]); 194 | state[2] += lo1 ^ hi2; 195 | 196 | state[3] ^= museair_read_u64(p + u64x(6)); 197 | state[4] ^= museair_read_u64(p + u64x(7)); 198 | MathMult::mult64_128(lo3, hi3, state[3], state[4]); 199 | state[3] += lo2 ^ hi3; 200 | 201 | state[4] ^= museair_read_u64(p + u64x(8)); 202 | state[5] ^= museair_read_u64(p + u64x(9)); 203 | MathMult::mult64_128(lo4, hi4, state[4], state[5]); 204 | state[4] += lo3 ^ hi4; 205 | 206 | state[5] ^= museair_read_u64(p + u64x(10)); 207 | state[0] ^= museair_read_u64(p + u64x(11)); 208 | MathMult::mult64_128(lo5, hi5, state[5], state[0]); 209 | state[5] += lo4 ^ hi5; 210 | } else { 211 | state[0] ^= museair_read_u64(p + u64x(0)); 212 | state[1] ^= museair_read_u64(p + u64x(1)); 213 | MathMult::mult64_128(lo0, hi0, state[0], state[1]); 214 | state[0] = lo5 ^ hi0; 215 | 216 | state[1] ^= museair_read_u64(p + u64x(2)); 217 | state[2] ^= museair_read_u64(p + u64x(3)); 218 | MathMult::mult64_128(lo1, hi1, state[1], state[2]); 219 | state[1] = lo0 ^ hi1; 220 | 221 | state[2] ^= museair_read_u64(p + u64x(4)); 222 | state[3] ^= museair_read_u64(p + u64x(5)); 223 | MathMult::mult64_128(lo2, hi2, state[2], state[3]); 224 | state[2] = lo1 ^ hi2; 225 | 226 | state[3] ^= museair_read_u64(p + u64x(6)); 227 | state[4] ^= museair_read_u64(p + u64x(7)); 228 | MathMult::mult64_128(lo3, hi3, state[3], state[4]); 229 | state[3] = lo2 ^ hi3; 230 | 231 | state[4] ^= museair_read_u64(p + u64x(8)); 232 | state[5] ^= museair_read_u64(p + u64x(9)); 233 | MathMult::mult64_128(lo4, hi4, state[4], state[5]); 234 | state[4] = lo3 ^ hi4; 235 | 236 | state[5] ^= museair_read_u64(p + u64x(10)); 237 | state[0] ^= museair_read_u64(p + u64x(11)); 238 | MathMult::mult64_128(lo5, hi5, state[5], state[0]); 239 | state[5] = lo4 ^ hi5; 240 | } 241 | 242 | p += u64x(12); 243 | q -= u64x(12); 244 | 245 | } while (likely(q >= u64x(12))); 246 | 247 | state[0] ^= lo5; 248 | } 249 | 250 | if (unlikely(q >= u64x(6))) { 251 | museair_mumix(&state[0], &state[1], museair_read_u64(p + u64x(0)), 252 | museair_read_u64(p + u64x(1))); 253 | museair_mumix(&state[2], &state[3], museair_read_u64(p + u64x(2)), 254 | museair_read_u64(p + u64x(3))); 255 | museair_mumix(&state[4], &state[5], museair_read_u64(p + u64x(4)), 256 | museair_read_u64(p + u64x(5))); 257 | 258 | p += u64x(6); 259 | q -= u64x(6); 260 | } 261 | 262 | if (likely(q >= u64x(2))) { 263 | museair_mumix(&state[0], &state[3], museair_read_u64(p + u64x(0)), 264 | museair_read_u64(p + u64x(1))); 265 | if (likely(q >= u64x(4))) { 266 | museair_mumix(&state[1], &state[4], museair_read_u64(p + u64x(2)), 267 | museair_read_u64(p + u64x(3))); 268 | } 269 | } 270 | 271 | museair_mumix(&state[2], &state[5], museair_read_u64(p + q - u64x(2)), 272 | museair_read_u64(p + q - u64x(1))); 273 | 274 | /*-------- epilogue --------*/ 275 | 276 | i = state[0] - state[1]; 277 | j = state[2] - state[3]; 278 | k = state[4] - state[5]; 279 | 280 | int rot = len & 63; 281 | i = ROTL64(i, rot); 282 | j = ROTR64(j, rot); 283 | k ^= len; 284 | 285 | MathMult::mult64_128(lo0, hi0, i, j); 286 | MathMult::mult64_128(lo1, hi1, j, k); 287 | MathMult::mult64_128(lo2, hi2, k, i); 288 | i = lo0 ^ hi2; 289 | j = lo1 ^ hi0; 290 | k = lo2 ^ hi1; 291 | 292 | if (b128) { 293 | MathMult::mult64_128(lo0, hi0, i, j); 294 | MathMult::mult64_128(lo1, hi1, j, k); 295 | MathMult::mult64_128(lo2, hi2, k, i); 296 | *out_lo = lo0 ^ lo1 ^ hi2; 297 | *out_hi = hi0 ^ hi1 ^ lo2; 298 | } else { 299 | MathMult::mult64_128(lo0, hi0, i, j); 300 | MathMult::mult64_128(lo1, hi1, j, k); 301 | MathMult::mult64_128(lo2, hi2, k, i); 302 | *out_lo = (lo0 ^ hi2) + (lo1 ^ hi0) + (lo2 ^ hi1); 303 | } 304 | } 305 | 306 | //------------------------------------------------------------------------------ 307 | // clang-format off 308 | REGISTER_FAMILY(MuseAir, 309 | $.src_url = "https://github.com/eternal-io/museair", 310 | $.src_status = HashFamilyInfo::SRC_ACTIVE 311 | ); 312 | 313 | REGISTER_HASH( 314 | MuseAir, 315 | $.desc = "MuseAir hash v" MUSEAIR_ALGORITHM_VERSION " @ 64-bit output", 316 | $.hash_flags = 0, 317 | $.impl_flags = FLAG_IMPL_MULTIPLY_64_128 318 | | FLAG_IMPL_ROTATE_VARIABLE 319 | | FLAG_IMPL_LICENSE_PUBLIC_DOMAIN, 320 | $.bits = 64, 321 | $.verification_LE = 0xF89F1683, 322 | $.verification_BE = 0xDFEF2570, 323 | $.hashfn_native = MuseAirHash, 324 | $.hashfn_bswap = MuseAirHash 325 | ); 326 | REGISTER_HASH( 327 | MuseAir_128, 328 | $.desc = "MuseAir hash v" MUSEAIR_ALGORITHM_VERSION " @ 128-bit output", 329 | $.hash_flags = 0, 330 | $.impl_flags = FLAG_IMPL_MULTIPLY_64_128 331 | | FLAG_IMPL_ROTATE_VARIABLE 332 | | FLAG_IMPL_LICENSE_PUBLIC_DOMAIN, 333 | $.bits = 128, 334 | $.verification_LE = 0xD3DFE238, 335 | $.verification_BE = 0x05EC3BE4, 336 | $.hashfn_native = MuseAirHash, 337 | $.hashfn_bswap = MuseAirHash 338 | ); 339 | 340 | REGISTER_HASH( 341 | MuseAir_BFast, 342 | $.desc = "MuseAir hash BFast variant v" MUSEAIR_ALGORITHM_VERSION " @ 64-bit output", 343 | $.hash_flags = 0, 344 | $.impl_flags = FLAG_IMPL_MULTIPLY_64_128 345 | | FLAG_IMPL_ROTATE_VARIABLE 346 | | FLAG_IMPL_LICENSE_PUBLIC_DOMAIN, 347 | $.bits = 64, 348 | $.verification_LE = 0xC61BEE56, 349 | $.verification_BE = 0x16186D00, 350 | $.hashfn_native = MuseAirHash, 351 | $.hashfn_bswap = MuseAirHash 352 | ); 353 | REGISTER_HASH( 354 | MuseAir_BFast_128, 355 | $.desc = "MuseAir hash BFast variant v" MUSEAIR_ALGORITHM_VERSION " @ 128-bit output", 356 | $.hash_flags = 0, 357 | $.impl_flags = FLAG_IMPL_MULTIPLY_64_128 358 | | FLAG_IMPL_ROTATE_VARIABLE 359 | | FLAG_IMPL_LICENSE_PUBLIC_DOMAIN, 360 | $.bits = 128, 361 | $.verification_LE = 0x27939BF1, 362 | $.verification_BE = 0xCB4AB283, 363 | $.hashfn_native = MuseAirHash, 364 | $.hashfn_bswap = MuseAirHash 365 | ); 366 | -------------------------------------------------------------------------------- /results/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | - SMHasher3 (34093a3 2024-06-17) 3 | ``` 4 | -------------------------------------------------------------------------------- /results/bench-smallkeys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eternal-io/museair/4261b3ce3625d505e027253a9858b1754615d399/results/bench-smallkeys.png -------------------------------------------------------------------------------- /results/plot-bench-smallkeys.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | from matplotlib import pyplot as plt 3 | 4 | STATS = ( 5 | ( 6 | "#2196F3", 7 | "rapidhash", 8 | "--", 9 | "19.61,18.59,18.59,12.70,25.81,25.81,25.81,12.39,25.47,25.47,25.47,12.04,12.17,12.17,12.17,12.17,29.58,29.58,29.58,29.74,29.58,29.58,29.42,29.42,29.42,29.42,29.42,29.42,29.42,29.42,29.42,29.58,34.51,34.51,34.51,34.51,34.51,34.32,34.32,34.32,34.32,34.32,34.32,34.32,34.32,34.32,34.32,34.32,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,31.16,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,37.59,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68,41.68", 10 | ), 11 | ( 12 | "#263238", 13 | "wyhash 4.2", 14 | "--", 15 | "19.06,19.00,19.00,12.81,27.94,27.94,27.94,12.39,26.29,26.29,26.29,12.74,12.81,12.81,12.88,12.81,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,29.58,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,35.33,32.05,32.22,32.05,32.05,32.05,32.22,32.05,32.05,32.22,32.87,32.87,32.87,32.87,32.87,32.83,32.05,33.04,36.35,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08,41.08", 16 | ), 17 | ( 18 | "#90A4AE", 19 | "wyhash.condom 4.2", 20 | "-", 21 | "18.80,19.31,19.31,13.42,28.09,27.94,27.94,13.22,27.26,27.26,27.26,13.22,13.29,13.29,13.29,13.29,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,31.22,36.15,36.15,36.15,36.15,36.15,36.16,36.15,36.16,36.15,36.15,36.15,36.15,36.15,36.15,36.15,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,33.04,38.00,38.00,38.00,38.00,38.00,38.00,38.00,38.00,37.80,38.00,38.00,38.00,38.00,38.00,38.00,37.80,42.72,42.96,42.96,42.96,42.72,42.72,42.72,42.73,42.73,42.72,42.96,42.72,42.96,42.96,42.72", 22 | ), 23 | ( 24 | "#7E57C2", 25 | "komihash 5.7", 26 | "-", 27 | "18.08,19.83,21.69,14.93,29.58,29.51,29.41,26.29,26.29,26.29,26.29,26.29,26.29,26.29,26.29,32.84,32.71,32.78,32.45,32.46,32.05,32.05,32.05,32.05,32.22,32.22,32.05,32.05,32.04,32.05,32.04,36.97,36.97,36.97,36.97,36.97,36.97,36.77,36.78,36.78,36.78,36.77,36.77,36.77,36.77,36.77,36.78,42.49,42.49,42.49,42.49,42.49,43.31,43.15,43.31,42.66,42.66,42.66,42.66,42.66,42.49,42.49,42.49,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,34.51,40.26,40.26,40.26,40.26,40.26,40.26,40.26,40.26,40.88,40.88,40.88,40.88,40.88,40.88,40.88,40.88", 28 | ), 29 | ) 30 | 31 | STATS_ = ( 32 | ( 33 | "#FBC02D", 34 | "MuseAir 0.3", 35 | "-", 36 | "19.52,19.69,19.69,13.08,27.79,27.79,27.79,13.08,26.97,26.97,26.97,13.77,13.77,13.77,13.77,13.77,16.69,16.69,16.69,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,18.80,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,36.90,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,35.96,40.04,40.04,40.04,40.04,40.04,40.05,40.05,40.04,40.05,40.04,40.04,40.04,40.04,40.04,40.05,40.04,41.68,41.91,41.91,41.76,42.08,41.69,41.68,41.68,41.86,41.68,41.68,41.68,41.68,41.68,41.68,41.68", 37 | ), 38 | ( 39 | "#F4511E", 40 | "MuseAir-BFast 0.3", 41 | "--", 42 | "17.98,18.70,18.90,12.60,26.15,26.29,26.29,12.81,26.29,26.15,26.15,12.67,12.81,12.81,12.81,12.81,16.52,16.52,16.52,17.18,17.18,17.18,17.18,17.18,17.18,17.18,17.18,17.09,17.09,17.18,17.09,17.18,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,35.06,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,36.15,37.79,37.79,37.79,37.79,37.79,37.59,37.79,37.79,37.79,37.79,37.79,37.79,37.79,37.79,37.79,37.79,39.85,39.86,40.26,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85,39.85", 43 | ), 44 | ) 45 | 46 | STATS2 = ( 47 | ( 48 | "#00C853", 49 | "rust-fxhash32", 50 | ":", 51 | "9.04,9.09,13.84,9.09,8.27,8.31,12.39,9.91,12.39,12.39,16.52,12.39,16.53,16.53,20.65,16.52,20.65,20.65,24.79,20.65,24.79,24.79,28.92,24.65,28.92,28.92,33.05,28.92,33.05,33.05,36.97,41.36", 52 | ), 53 | ( 54 | "#004D40", 55 | "rust-fxhash64", 56 | ":", 57 | "9.04,9.09,14.62,9.09,8.27,9.09,12.40,18.90,23.01,23.01,27.11,23.70,27.11,27.94,31.22,23.01,27.11,27.11,31.22,27.11,31.22,31.22,35.33,27.11,31.22,31.22,35.33,31.22,35.33,35.33,39.44,31.22", 58 | ), 59 | ) 60 | 61 | 62 | plt.figure(figsize=(14, 5)) 63 | 64 | for color, name, dashed, dat in chain(STATS, STATS_): 65 | plt.plot( 66 | list(range(1, 96)), 67 | [float(d) for d in dat.split(",")], 68 | dashed, 69 | c=color + "C0", 70 | label=name, 71 | linewidth=2.5, 72 | ) 73 | for color, name, dashed, dat in STATS2: 74 | plt.plot( 75 | list(range(1, 33)), 76 | [float(d) for d in dat.split(",")], 77 | dashed, 78 | c=color + "E0", 79 | label=name, 80 | linewidth=1.2, 81 | ) 82 | 83 | plt.setp(plt.legend().get_lines(), linewidth=1.5) 84 | plt.xlim(1, 95) 85 | plt.xticks(list(range(1, 8)) + list(range(8, 96, 4))) 86 | plt.xlabel("N-byte keys") 87 | plt.ylabel("Cycles") 88 | plt.grid(color="#CCC", linestyle="--", linewidth=0.5) 89 | plt.title("Latency for small keys (lower is better)") 90 | plt.tight_layout() 91 | plt.savefig("bench-smallkeys.png") 92 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /show-asm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "show-asm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | path = "lib.rs" 9 | 10 | [dependencies] 11 | museair = { path = ".." } 12 | rapidhash = "1.2.0" 13 | -------------------------------------------------------------------------------- /show-asm/lib.rs: -------------------------------------------------------------------------------- 1 | #[inline(never)] 2 | pub fn hash_standard(bytes: &[u8], seed: u64) -> u64 { 3 | museair::hash(bytes, seed) 4 | } 5 | 6 | #[inline(never)] 7 | pub fn hash_bfast(bytes: &[u8], seed: u64) -> u64 { 8 | museair::bfast::hash(bytes, seed) 9 | } 10 | 11 | #[inline(never)] 12 | pub fn rapidhash(bytes: &[u8], seed: u64) -> u64 { 13 | rapidhash::rapidhash_inline(bytes, seed) 14 | } 15 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 K--Aethiax 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Alternatively, the contents of this file may be used under the terms of 17 | * the MIT license as described below. 18 | * 19 | * Permission is hereby granted, free of charge, to any person obtaining a copy 20 | * of this software and associated documentation files (the "Software"), to deal 21 | * in the Software without restriction, including without limitation the rights 22 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | * copies of the Software, and to permit persons to whom the Software is 24 | * furnished to do so, subject to the following conditions: 25 | * 26 | * The above copyright notice and this permission notice shall be included in all 27 | * copies or substantial portions of the Software. 28 | * 29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | * SOFTWARE. 36 | */ 37 | 38 | #![no_std] 39 | #![doc = include_str!("../CRATES.IO-README.md")] 40 | #![doc(html_logo_url = "https://github.com/eternal-io/museair/blob/master/MuseAir-icon-light.png?raw=true")] 41 | #![warn(missing_docs)] 42 | 43 | /// Currently algorithm version. 44 | /// 45 | /// Note that this is **NOT** the implementation version. 46 | /// 47 | /// If you want to see an older version of the algorithm, check out the historical commits 48 | /// for [`museair.cpp`](https://github.com/eternal-io/museair/blob/master/museair.cpp) in repository root. 49 | pub const ALGORITHM_VERSION: &str = "0.3"; 50 | 51 | /// Incremental MuseAir hasher. 52 | /// 53 | /// Due to its incremental nature, it's much slower than one-shot functions for small keys. 54 | /// Consider [musemap](https://crates.io/crates/musemap) for hashmap use case. 55 | pub type Hasher = BaseHasher; 56 | 57 | /// One-shot MuseAir hash with 64-bit output. 58 | #[inline] 59 | pub fn hash(bytes: &[u8], seed: u64) -> u64 { 60 | base_hash_64::(bytes, seed) 61 | } 62 | 63 | /// One-shot MuseAir hash with 128-bit output. 64 | #[inline] 65 | pub fn hash_128(bytes: &[u8], seed: u64) -> u128 { 66 | base_hash_128::(bytes, seed) 67 | } 68 | 69 | /// The `BFast` variant. 70 | pub mod bfast { 71 | use super::*; 72 | 73 | /// Incremental MuseAir-BFast hasher. 74 | /// 75 | /// Due to its incremental nature, it's much slower than one-shot functions for small keys. 76 | /// Consider [musemap](https://crates.io/crates/musemap) for hashmap use case. 77 | pub type Hasher = BaseHasher; 78 | 79 | /// One-shot MuseAir-BFast hash with 64-bit output. 80 | #[inline] 81 | pub fn hash(bytes: &[u8], seed: u64) -> u64 { 82 | base_hash_64::(bytes, seed) 83 | } 84 | 85 | /// One-shot MuseAir-BFast hash with 128-bit output. 86 | #[inline] 87 | pub fn hash_128(bytes: &[u8], seed: u64) -> u128 { 88 | base_hash_128::(bytes, seed) 89 | } 90 | } 91 | 92 | //-------------------------------------------------------------------------------------------------- 93 | 94 | /// `AiryAi(0)` mantissa calculated by Y-Cruncher. 95 | const CONSTANT: [u64; 7] = [ 96 | 0x5ae31e589c56e17a, 97 | 0x96d7bb04e64f6da9, 98 | 0x7ab1006b26f9eb64, 99 | 0x21233394220b8457, 100 | 0x047cb9557c9f3b43, 101 | 0xd24f2590c0bcee28, 102 | 0x33ea8f71bb6016d8, 103 | ]; 104 | 105 | /// Lower 64-bit, then upper 64-bit. 106 | #[inline(always)] 107 | const fn wmul(a: u64, b: u64) -> (u64, u64) { 108 | u128_to_u64s(a as u128 * b as u128) 109 | } 110 | 111 | /// Lower 64-bit, then upper 64-bit. 112 | #[inline(always)] 113 | const fn u128_to_u64s(x: u128) -> (u64, u64) { 114 | (x as u64, (x >> 64) as u64) 115 | } 116 | /// Lower 64-bit, then upper 64-bit. 117 | #[inline(always)] 118 | const fn u64s_to_u128(lo: u64, hi: u64) -> u128 { 119 | ((hi as u128) << 64) | lo as u128 120 | } 121 | 122 | #[cold] 123 | #[inline(always)] 124 | const fn cold_path() {} 125 | 126 | #[inline(always)] 127 | const fn likely(cond: bool) -> bool { 128 | if !cond { 129 | cold_path(); 130 | } 131 | cond 132 | } 133 | #[inline(always)] 134 | const fn unlikely(cond: bool) -> bool { 135 | if cond { 136 | cold_path(); 137 | } 138 | cond 139 | } 140 | 141 | macro_rules! u64x { 142 | ($n:literal) => { 143 | $n * 8 144 | }; 145 | } 146 | 147 | /*------------------------------- 🛰 Safe Rust only slows me down. -------------------------------*/ 148 | 149 | use core::{fmt, ops::Sub}; 150 | 151 | #[inline(always)] 152 | unsafe fn read_u32(bytes: *const u8) -> u64 { 153 | u32::from_le_bytes(*(bytes as *const [u8; 4])) as u64 154 | } 155 | 156 | #[inline(always)] 157 | unsafe fn read_u64(bytes: *const u8) -> u64 { 158 | u64::from_le_bytes(*(bytes as *const [u8; 8])) 159 | } 160 | 161 | #[inline(always)] 162 | unsafe fn read_short(bytes: *const u8, len: usize) -> (u64, u64) { 163 | if len >= 4 { 164 | let off = (len & 24) >> (len >> 3); 165 | ( 166 | (read_u32(bytes) << 32) | read_u32(bytes.add(len - 4)), 167 | (read_u32(bytes.add(off)) << 32) | read_u32(bytes.add(len - 4 - off)), 168 | ) 169 | } else if len > 0 { 170 | ( 171 | // MSB <-> LSB 172 | // [0] [0] [0] @ len == 1 (0b01) 173 | // [0] [1] [1] @ len == 2 (0b10) 174 | // [0] [1] [2] @ len == 3 (0b11) 175 | ((*bytes as u64) << 48) | ((*bytes.add(len >> 1) as u64) << 24) | (*bytes.add(len - 1) as u64), 176 | 0, 177 | ) 178 | } else { 179 | (0, 0) 180 | } 181 | } 182 | 183 | #[inline(always)] 184 | fn _mumix(mut state_p: u64, mut state_q: u64, input_p: u64, input_q: u64) -> (u64, u64) { 185 | if !BFAST { 186 | state_p ^= input_p; 187 | state_q ^= input_q; 188 | let (lo, hi) = wmul(state_p, state_q); 189 | (state_p ^ lo, state_q ^ hi) 190 | } else { 191 | wmul(state_p ^ input_p, state_q ^ input_q) 192 | } 193 | } 194 | 195 | //-------------------------------------------------------------------------------------------------- 196 | 197 | #[inline(always)] 198 | fn hash_short_64(bytes: &[u8], seed: u64) -> u64 { 199 | let (mut i, mut j) = unsafe { hash_short_common(bytes.as_ptr(), bytes.len(), seed) }; 200 | 201 | let (lo, hi) = wmul(i ^ CONSTANT[2], j ^ CONSTANT[3]); 202 | if !BFAST { 203 | i ^= lo; 204 | j ^= hi; 205 | } else { 206 | i = lo; 207 | j = hi; 208 | } 209 | 210 | let (lo, hi) = wmul(i ^ CONSTANT[4], j ^ CONSTANT[5]); 211 | if !BFAST { 212 | i ^ j ^ lo ^ hi 213 | } else { 214 | lo ^ hi 215 | } 216 | } 217 | 218 | #[inline(always)] 219 | fn hash_short_128(bytes: &[u8], seed: u64) -> u128 { 220 | let (mut i, mut j) = unsafe { hash_short_common(bytes.as_ptr(), bytes.len(), seed) }; 221 | let (lo0, hi0) = wmul(i, j); 222 | let (lo1, hi1) = wmul(i ^ CONSTANT[2], j ^ CONSTANT[3]); 223 | i = lo0 ^ hi1; 224 | j = lo1 ^ hi0; 225 | let (lo0, hi0) = wmul(i, j); 226 | let (lo1, hi1) = wmul(i ^ CONSTANT[4], j ^ CONSTANT[5]); 227 | u64s_to_u128(lo0 ^ hi1, lo1 ^ hi0) 228 | } 229 | 230 | #[inline(always)] 231 | unsafe fn hash_short_common(bytes: *const u8, len: usize, seed: u64) -> (u64, u64) { 232 | let len_ = len as u64; 233 | let (lo, hi) = wmul(seed ^ CONSTANT[0], len_ ^ CONSTANT[1]); 234 | 235 | let (mut i, mut j) = read_short(bytes, len.min(u64x!(2))); 236 | i ^= len_ ^ lo; 237 | j ^= seed ^ hi; 238 | 239 | if unlikely(len > u64x!(2)) { 240 | let (u, v) = read_short(bytes.add(u64x!(2)), len.sub(u64x!(2))); 241 | let (lo0, hi0) = wmul(CONSTANT[2], CONSTANT[3] ^ u); 242 | let (lo1, hi1) = wmul(CONSTANT[4], CONSTANT[5] ^ v); 243 | i ^= lo0 ^ hi1; 244 | j ^= lo1 ^ hi0; 245 | } 246 | 247 | (i, j) 248 | } 249 | 250 | //-------------------------------------------------------------------------------------------------- 251 | 252 | #[inline(never)] 253 | fn hash_loong_64(bytes: &[u8], seed: u64) -> u64 { 254 | unsafe { epilogue_64(hash_loong_common::(bytes.as_ptr(), bytes.len(), seed)) } 255 | } 256 | 257 | #[inline(never)] 258 | fn hash_loong_128(bytes: &[u8], seed: u64) -> u128 { 259 | unsafe { epilogue_128(hash_loong_common::(bytes.as_ptr(), bytes.len(), seed)) } 260 | } 261 | 262 | #[inline(always)] 263 | unsafe fn hash_loong_common(bytes: *const u8, len: usize, seed: u64) -> (u64, u64, u64) { 264 | let mut p = bytes; 265 | let mut q = len; 266 | 267 | let mut state = [ 268 | CONSTANT[0].wrapping_add(seed), 269 | CONSTANT[1].wrapping_sub(seed), 270 | CONSTANT[2] ^ seed, 271 | CONSTANT[3].wrapping_add(seed), 272 | CONSTANT[4].wrapping_sub(seed), 273 | CONSTANT[5] ^ seed, 274 | ]; 275 | 276 | if unlikely(q >= u64x!(12)) { 277 | let mut ring_prev = CONSTANT[6]; 278 | 279 | while likely(q >= u64x!(12)) { 280 | ring_prev = frac_tower::(&mut state, ring_prev, p); 281 | p = p.add(u64x!(12)); 282 | q = q.sub(u64x!(12)); 283 | } 284 | 285 | state[0] ^= ring_prev; 286 | } 287 | 288 | frac_final::(&mut state, p, q); 289 | 290 | epilogue_common(state, len) 291 | } 292 | 293 | /// Must remainder `u64x!(12)` or more bytes otherwise UB. 294 | #[inline(always)] 295 | unsafe fn frac_tower(state: &mut [u64; 6], ring_prev: u64, p: *const u8) -> u64 { 296 | if !BFAST { 297 | state[0] ^= read_u64(p.add(u64x!(0))); 298 | state[1] ^= read_u64(p.add(u64x!(1))); 299 | let (lo0, hi0) = wmul(state[0], state[1]); 300 | state[0] = state[0].wrapping_add(ring_prev ^ hi0); 301 | 302 | state[1] ^= read_u64(p.add(u64x!(2))); 303 | state[2] ^= read_u64(p.add(u64x!(3))); 304 | let (lo1, hi1) = wmul(state[1], state[2]); 305 | state[1] = state[1].wrapping_add(lo0 ^ hi1); 306 | 307 | state[2] ^= read_u64(p.add(u64x!(4))); 308 | state[3] ^= read_u64(p.add(u64x!(5))); 309 | let (lo2, hi2) = wmul(state[2], state[3]); 310 | state[2] = state[2].wrapping_add(lo1 ^ hi2); 311 | 312 | state[3] ^= read_u64(p.add(u64x!(6))); 313 | state[4] ^= read_u64(p.add(u64x!(7))); 314 | let (lo3, hi3) = wmul(state[3], state[4]); 315 | state[3] = state[3].wrapping_add(lo2 ^ hi3); 316 | 317 | state[4] ^= read_u64(p.add(u64x!(8))); 318 | state[5] ^= read_u64(p.add(u64x!(9))); 319 | let (lo4, hi4) = wmul(state[4], state[5]); 320 | state[4] = state[4].wrapping_add(lo3 ^ hi4); 321 | 322 | state[5] ^= read_u64(p.add(u64x!(10))); 323 | state[0] ^= read_u64(p.add(u64x!(11))); 324 | let (lo5, hi5) = wmul(state[5], state[0]); 325 | state[5] = state[5].wrapping_add(lo4 ^ hi5); 326 | 327 | lo5 328 | } else { 329 | state[0] ^= read_u64(p.add(u64x!(0))); 330 | state[1] ^= read_u64(p.add(u64x!(1))); 331 | let (lo0, hi0) = wmul(state[0], state[1]); 332 | state[0] = ring_prev ^ hi0; 333 | 334 | state[1] ^= read_u64(p.add(u64x!(2))); 335 | state[2] ^= read_u64(p.add(u64x!(3))); 336 | let (lo1, hi1) = wmul(state[1], state[2]); 337 | state[1] = lo0 ^ hi1; 338 | 339 | state[2] ^= read_u64(p.add(u64x!(4))); 340 | state[3] ^= read_u64(p.add(u64x!(5))); 341 | let (lo2, hi2) = wmul(state[2], state[3]); 342 | state[2] = lo1 ^ hi2; 343 | 344 | state[3] ^= read_u64(p.add(u64x!(6))); 345 | state[4] ^= read_u64(p.add(u64x!(7))); 346 | let (lo3, hi3) = wmul(state[3], state[4]); 347 | state[3] = lo2 ^ hi3; 348 | 349 | state[4] ^= read_u64(p.add(u64x!(8))); 350 | state[5] ^= read_u64(p.add(u64x!(9))); 351 | let (lo4, hi4) = wmul(state[4], state[5]); 352 | state[4] = lo3 ^ hi4; 353 | 354 | state[5] ^= read_u64(p.add(u64x!(10))); 355 | state[0] ^= read_u64(p.add(u64x!(11))); 356 | let (lo5, hi5) = wmul(state[5], state[0]); 357 | state[5] = lo4 ^ hi5; 358 | 359 | lo5 360 | } 361 | } 362 | 363 | /// Must remainder `u64x!(2)` or more bytes otherwise UB. 364 | #[inline(always)] 365 | unsafe fn frac_final(state: &mut [u64; 6], mut p: *const u8, mut q: usize) { 366 | if unlikely(q >= u64x!(6)) { 367 | (state[0], state[1]) = 368 | _mumix::(state[0], state[1], read_u64(p.add(u64x!(0))), read_u64(p.add(u64x!(1)))); 369 | (state[2], state[3]) = 370 | _mumix::(state[2], state[3], read_u64(p.add(u64x!(2))), read_u64(p.add(u64x!(3)))); 371 | (state[4], state[5]) = 372 | _mumix::(state[4], state[5], read_u64(p.add(u64x!(4))), read_u64(p.add(u64x!(5)))); 373 | p = p.add(u64x!(6)); 374 | q = q.sub(u64x!(6)); 375 | } 376 | 377 | if likely(q >= u64x!(2)) { 378 | (state[0], state[3]) = 379 | _mumix::(state[0], state[3], read_u64(p.add(u64x!(0))), read_u64(p.add(u64x!(1)))); 380 | if likely(q >= u64x!(4)) { 381 | (state[1], state[4]) = 382 | _mumix::(state[1], state[4], read_u64(p.add(u64x!(2))), read_u64(p.add(u64x!(3)))); 383 | } 384 | } 385 | 386 | (state[2], state[5]) = _mumix::( 387 | state[2], 388 | state[5], 389 | read_u64(p.add(q).sub(u64x!(2))), 390 | read_u64(p.add(q).sub(u64x!(1))), 391 | ); 392 | } 393 | 394 | // Note that only `_loong` requires separated `epilogue`. 395 | 396 | #[inline(always)] 397 | fn epilogue_common(state: [u64; 6], tot_len: usize) -> (u64, u64, u64) { 398 | let mut i = state[0].wrapping_sub(state[1]); 399 | let mut j = state[2].wrapping_sub(state[3]); 400 | let mut k = state[4].wrapping_sub(state[5]); 401 | 402 | let rot = tot_len as u32 & 63; 403 | i = i.rotate_left(rot); 404 | j = j.rotate_right(rot); 405 | k ^= tot_len as u64; 406 | 407 | let (lo0, hi0) = wmul(i, j); 408 | let (lo1, hi1) = wmul(j, k); 409 | let (lo2, hi2) = wmul(k, i); 410 | i = lo0 ^ hi2; 411 | j = lo1 ^ hi0; 412 | k = lo2 ^ hi1; 413 | 414 | (i, j, k) 415 | } 416 | 417 | #[inline(always)] 418 | fn epilogue_64(triple: (u64, u64, u64)) -> u64 { 419 | let (i, j, k) = triple; 420 | let (lo0, hi0) = wmul(i, j); 421 | let (lo1, hi1) = wmul(j, k); 422 | let (lo2, hi2) = wmul(k, i); 423 | (lo0 ^ hi2).wrapping_add(lo1 ^ hi0).wrapping_add(lo2 ^ hi1) 424 | } 425 | 426 | #[inline(always)] 427 | fn epilogue_128(triple: (u64, u64, u64)) -> u128 { 428 | let (i, j, k) = triple; 429 | let (lo0, hi0) = wmul(i, j); 430 | let (lo1, hi1) = wmul(j, k); 431 | let (lo2, hi2) = wmul(k, i); 432 | u64s_to_u128(lo0 ^ lo1 ^ hi2, hi0 ^ hi1 ^ lo2) 433 | } 434 | 435 | //-------------------------------------------------------------------------------------------------- 436 | 437 | #[inline(always)] 438 | fn base_hash_64(bytes: &[u8], seed: u64) -> u64 { 439 | if likely(bytes.len() <= u64x!(4)) { 440 | hash_short_64::(bytes, seed) 441 | } else { 442 | hash_loong_64::(bytes, seed) 443 | } 444 | } 445 | 446 | #[inline(always)] 447 | fn base_hash_128(bytes: &[u8], seed: u64) -> u128 { 448 | if likely(bytes.len() <= u64x!(4)) { 449 | hash_short_128(bytes, seed) 450 | } else { 451 | hash_loong_128::(bytes, seed) 452 | } 453 | } 454 | 455 | /// Common incremental hasher that implemented both `Standard` and `BFast` variants. 456 | /// Use [`Hasher`] or [`bfast::Hasher`] type aliases. 457 | #[repr(C)] 458 | #[derive(Clone)] 459 | pub struct BaseHasher { 460 | /// It is guaranteed `buffered_len < 96` after arbitrary write, 461 | /// that is to say `frac_tower` is eager executed. 462 | buffer: [u8; 96], 463 | buffered_len: usize, 464 | processed_len: u64, 465 | 466 | state: [u64; 6], 467 | ring_prev: u64, 468 | } 469 | 470 | #[allow(missing_docs)] 471 | impl BaseHasher { 472 | pub const fn new() -> Self { 473 | Self::with_seed(0) 474 | } 475 | 476 | pub const fn with_seed(seed: u64) -> Self { 477 | Self { 478 | buffer: [0x00; 96], 479 | buffered_len: 0, 480 | processed_len: 0, 481 | 482 | state: [ 483 | CONSTANT[0].wrapping_add(seed), 484 | CONSTANT[1].wrapping_sub(seed), 485 | CONSTANT[2] ^ seed, 486 | CONSTANT[3].wrapping_add(seed), 487 | CONSTANT[4].wrapping_sub(seed), 488 | CONSTANT[5] ^ seed, 489 | ], 490 | ring_prev: CONSTANT[6], 491 | } 492 | } 493 | 494 | #[inline(always)] 495 | fn restore_seed(&self) -> u64 { 496 | debug_assert!(self.processed_len == 0); 497 | self.state[0].wrapping_sub(CONSTANT[0]) 498 | } 499 | 500 | pub fn write(&mut self, mut bytes: &[u8]) { 501 | let vacancy = u64x!(12) - self.buffered_len; 502 | if bytes.len() < vacancy { 503 | self.buffer[self.buffered_len..][..bytes.len()].copy_from_slice(bytes); 504 | self.buffered_len += bytes.len(); 505 | return; 506 | } else { 507 | self.buffer[self.buffered_len..][..vacancy].copy_from_slice(&bytes[..vacancy]); 508 | self.buffered_len = 0; 509 | bytes = &bytes[vacancy..]; 510 | } 511 | 512 | self.ring_prev = unsafe { frac_tower::(&mut self.state, self.ring_prev, self.buffer.as_ptr()) }; 513 | self.processed_len = self.processed_len.wrapping_add(u64x!(12)); 514 | while likely(bytes.len() >= u64x!(12)) { 515 | self.ring_prev = unsafe { frac_tower::(&mut self.state, self.ring_prev, bytes.as_ptr()) }; 516 | self.processed_len = self.processed_len.wrapping_add(u64x!(12)); 517 | bytes = &bytes[u64x!(12)..]; 518 | } 519 | 520 | self.buffer[..bytes.len()].copy_from_slice(bytes); 521 | self.buffered_len = bytes.len(); 522 | } 523 | 524 | #[inline(always)] 525 | fn contiguous_remainder(&self) -> ([u8; 96], usize) { 526 | let (younger, older) = self.buffer.split_at(self.buffered_len); 527 | let mut buffer = [0x00; 96]; 528 | buffer[..older.len()].copy_from_slice(older); 529 | buffer[older.len()..].copy_from_slice(younger); 530 | (buffer, older.len()) 531 | } 532 | 533 | pub fn finish(&self) -> u64 { 534 | let tot_len = self.processed_len.wrapping_add(self.buffered_len as u64); 535 | if unlikely(tot_len <= u64x!(4)) { 536 | hash_short_64::(&self.buffer[..self.buffered_len], self.restore_seed()) 537 | } else { 538 | let (remainder, delta) = self.contiguous_remainder(); 539 | let mut state = self.state; 540 | if likely(tot_len >= u64x!(12)) { 541 | state[0] ^= self.ring_prev; 542 | } 543 | unsafe { frac_final::(&mut state, remainder.as_ptr().add(delta), self.buffered_len) }; 544 | epilogue_64(epilogue_common(state, tot_len as usize)) 545 | } 546 | } 547 | 548 | pub fn finish_128(&self) -> u128 { 549 | let tot_len = self.processed_len.wrapping_add(self.buffered_len as u64); 550 | if unlikely(tot_len <= u64x!(4)) { 551 | hash_short_128(&self.buffer[..self.buffered_len], self.restore_seed()) 552 | } else { 553 | let (remainder, delta) = self.contiguous_remainder(); 554 | let mut state = self.state; 555 | if likely(tot_len >= u64x!(12)) { 556 | state[0] ^= self.ring_prev; 557 | } 558 | unsafe { frac_final::(&mut state, remainder.as_ptr().add(delta), self.buffered_len) }; 559 | epilogue_128(epilogue_common(state, tot_len as usize)) 560 | } 561 | } 562 | } 563 | 564 | impl Default for BaseHasher { 565 | fn default() -> Self { 566 | Self::new() 567 | } 568 | } 569 | 570 | impl fmt::Debug for BaseHasher { 571 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 572 | match !BFAST { 573 | true => f.write_str("museair::Hasher { ... }"), 574 | false => f.write_str("museair::bfast::Hasher { ... }"), 575 | } 576 | } 577 | } 578 | 579 | impl core::hash::Hasher for BaseHasher { 580 | fn finish(&self) -> u64 { 581 | self.finish() 582 | } 583 | 584 | fn write(&mut self, bytes: &[u8]) { 585 | self.write(bytes); 586 | } 587 | } 588 | 589 | //-------------------------------------------------------------------------------------------------- 590 | 591 | #[cfg(all(test, target_endian = "little"))] 592 | mod verify { 593 | use super::*; 594 | extern crate std; 595 | 596 | #[test] 597 | fn verification_code() { 598 | assert_eq!( 599 | 0xF89F1683, 600 | hashverify::compute(64, |bytes, seed, out| out 601 | .copy_from_slice(&hash(bytes, seed).to_le_bytes())) 602 | ); 603 | assert_eq!( 604 | 0xD3DFE238, 605 | hashverify::compute(128, |bytes, seed, out| out 606 | .copy_from_slice(&hash_128(bytes, seed).to_le_bytes())) 607 | ); 608 | assert_eq!( 609 | 0xC61BEE56, 610 | hashverify::compute(64, |bytes, seed, out| out 611 | .copy_from_slice(&bfast::hash(bytes, seed).to_le_bytes())) 612 | ); 613 | assert_eq!( 614 | 0x27939BF1, 615 | hashverify::compute(128, |bytes, seed, out| out 616 | .copy_from_slice(&bfast::hash_128(bytes, seed).to_le_bytes())) 617 | ); 618 | } 619 | 620 | #[test] 621 | fn one_shot_eq_streamed() { 622 | macro_rules! one_shot_vs_streamed { 623 | ($hash:path, $hasher:ty, $finish:ident) => { 624 | for n in 0..1024 { 625 | let bytes = std::vec![0xAB; n]; 626 | let one_shot = $hash(&bytes, n as u64); 627 | let streamed = { 628 | let mut hasher = <$hasher>::with_seed(n as u64); 629 | let (x, y, z) = random_split(&bytes); 630 | hasher.write(x); 631 | hasher.write(y); 632 | hasher.write(z); 633 | hasher.$finish() 634 | }; 635 | assert_eq!(one_shot, streamed, "len == {}", n); 636 | } 637 | }; 638 | } 639 | 640 | one_shot_vs_streamed!(hash, Hasher, finish); 641 | one_shot_vs_streamed!(hash_128, Hasher, finish_128); 642 | one_shot_vs_streamed!(bfast::hash, bfast::Hasher, finish); 643 | one_shot_vs_streamed!(bfast::hash_128, bfast::Hasher, finish_128); 644 | } 645 | 646 | fn random_split(bytes: &[u8]) -> (&[u8], &[u8], &[u8]) { 647 | match bytes.len() as u64 { 648 | 0 => (&[], &[], &[]), 649 | 1 => (&bytes[0..1], &[], &[]), 650 | 2 => (&bytes[0..1], &bytes[1..2], &[]), 651 | 3 => (&bytes[0..1], &bytes[1..2], &bytes[2..3]), 652 | n => { 653 | let p = wyhash::wyrng(&mut n.clone()) % (n - 2); 654 | let q = wyhash::wyrng(&mut !n) % (n - p); 655 | let (x, y) = bytes.split_at(p as usize); 656 | let (y, z) = y.split_at(q as usize); 657 | (x, y, z) 658 | } 659 | } 660 | } 661 | } 662 | --------------------------------------------------------------------------------