SipHash-2-4, the add-rotate-xor pseudorandom function of Aumasson & Bernstein, "SipHash: a fast short-input PRF", in pure Standard ML.
No dependencies, no FFI, no threads, no clock, no randomness: the same
128-bit key and message always produce the same 64-bit output under
MLton and Poly/ML. All internal state and arithmetic uses
Word64.word explicitly — never the default Word type, whose width
differs between MLton (32-bit) and Poly/ML (63-bit) and would otherwise
silently diverge between compilers.
SipHash-2-4 runs 2 compression rounds per 8-byte message block and 4 finalization rounds. (SipHash-1-3 is not implemented; scope was kept to the 2-4 variant used almost everywhere SipHash appears, e.g. hash-DoS-resistant hash tables and OS PRFs.)
signature SIPHASH =
sig
(* hash {k0, k1} msg: SipHash-2-4 of `msg` under 128-bit key (k0, k1). *)
val hash : {k0 : Word64.word, k1 : Word64.word} -> string -> Word64.word
(* hash the message as a hex-encoded (lowercase, 16 hex digits) string. *)
val hashHex : {k0 : Word64.word, k1 : Word64.word} -> string -> string
endThe 128-bit key is split into two Word64.word halves k0/k1, matching
the reference C implementation's little-endian key layout (k0 from key
bytes 0..7, k1 from bytes 8..15, each read little-endian). The message is
an SML string consumed byte-by-byte (Char.ord of each character, so
binary messages are represented as strings of chars 0..255).
val key = { k0 = 0wx0706050403020100 : Word64.word
, k1 = 0wx0f0e0d0c0b0a0908 : Word64.word }
val h = Siphash.hash key "hello" (* : Word64.word *)
val hex = Siphash.hashHex key "hello" (* "004fb3985767df81" *)Running examples/demo.sml with make example prints:
SipHash-2-4, key 000102030405060708090a0b0c0d0e0f:
hash("") = 726fdb47dd0e0e31
hash("a") = 2ba3e8e9a71148ca
hash("hello") = 004fb3985767df81
hash("sjqtentacles") = 1b014be91f4a492d
Same message, different keys (key sensitivity):
key A -> 6a571e45426093e9
key B -> 2163a21c114a0d9b
Requires MLton and/or Poly/ML.
make test # build + run the suite under MLton
make test-poly # run the suite under Poly/ML
make all-tests # both + byte-identical output gate
make example # build + run the demo
make cleansmlpkg add github.com/sjqtentacles/sml-siphash
smlpkg syncReference lib/github.com/sjqtentacles/sml-siphash/siphash.mlb from your own
.mlb (MLton / MLKit), or feed sources.mlb to tools/polybuild (Poly/ML).
sml.pkg smlpkg manifest
Makefile MLton + Poly/ML targets
.github/workflows/ci.yml CI: MLton + Poly/ML
lib/github.com/sjqtentacles/sml-siphash/
siphash.sig SIPHASH signature
siphash.sml SipHash-2-4 implementation
sources.mlb ordered source list
siphash.mlb public basis
examples/
demo.sml fixed-key hash walkthrough
test/
harness.sml shared assertion harness
test.sml canonical vectors + key/message sensitivity checks (18 checks)
entry.sml / main.sml
tools/polybuild Poly/ML build wrapper
18 deterministic checks, anchored on the canonical SipHash-2-4 conformance
table (all 64 outputs, message lengths 0..63) from the reference C
implementation's
vectors.h —
key 000102030405060708090a0b0c0d0e0f, message byte i has value i —
the same table reproduced in the SipHash paper's appendix and used as the
standard cross-implementation test by essentially every SipHash port. Also
covers determinism, hex-encoding format, and key/message sensitivity. Run
make all-tests to verify identical output under both compilers.
MIT. See LICENSE.