This dataset is being compiled while we research realistic Argon2id parameter choices for the Who Technologies mTOTP protocol; we're publishing the results because the data is likely useful to the broader community working with Argon2. The full raw dataset (one row per measured hash) will be released free for anyone to use once ready.
Each cell is one (m × t) parameter point at p = 1.
Defender platforms (Android, iOS, Desktop/Server) hash 1-3 times (depending on the
speed of the hash) which are averaged together for that device.
Attacker (GPU) uses hashcat --speed-only to measure steady-state throughput.
The heatmap always shows one platform at a time so defender and attacker results are never mixed.
Android: lazysodium-android 5.1.0 (libsodium JNI bindings). Desktop/Server: PyNaCl (libsodium Python bindings; version varies by host). GPU: hashcat v7.1.2 (patched), mode 34000.
Three patches were applied to upstream hashcat v7.1.2:
len_max=5, rejecting t ≥ 1000.
Raised to 12 to cover the full parameter grid.--machine-readable output, derived from hashcat's
internal double. The existing integer H/s field truncates
sub-1 H/s rates to zero, losing all data for slow parameter
combinations where one hash takes seconds to minutes.--speed-only has a
hardcoded 4 s guard in choose_kernel() that reports
0 H/s when one hash exceeds it. Raised to 15 s
(15000 ms).On all platforms, once a measured hash exceeds a wall-clock cutoff the benchmark stops measuring further (more expensive) cells in that memory column and extrapolates from prior data instead. Estimated cells are shown with a grey diagonal-stripe overlay running the opposite way to the OOM markers: a thin stripe when the cell blends measured devices with estimated ones, a slightly thicker one when every value in the cell is an estimate (hover a cell for the exact counts). Estimates are calculated as:
pass_1_cost = ns(lowest_t in column)
per_pass_cost = avg( (ns(t) - pass_1_cost) / (t - lowest_t) ) for all measured t > lowest_t
estimate(t_target) = pass_1_cost + (t_target - lowest_t) * per_pass_cost
...which takes into account the slower first pass. From testing, this estimate is more
than 99% accurate at the higher m difficulties. For lower m difficulties where timing noise
plays a larger role, we directly test each cell to get more accurate data. When both real
and estimated data exist for a cell, the real measurement is used.
Blank cells mean no measurement exists for that parameter combination — the cell was not tested (or was dropped for lost precision).
OOM events are bucketed per memory column (memory cost drives peak allocation, not time cost). The lowest memory level at which a device has ever OOMed is its memory ceiling: Argon2id's footprint is set by memory cost alone, so the device cannot run any column at or above it, and the marker covers every column from the ceiling to the end of the grid even where no run recorded an OOM. The marker counts devices in the current filter whose ceiling is at or below the column:
Columns below every shown device's ceiling get no marker, even if data is missing. A cell that is both estimated and inside an OOM-marked column carries both hatches, crossing: the grey estimate stripes run one way, the coloured OOM stripes the other. If you're selecting Argon2id parameters for a mobile app, you probably want to stay a reasonable distance away from these orange and red zones.
When multiple devices match the filters, each cell aggregates across all matching runs. The target value sets the color dividing line: ±50% of target renders blue, faster is green, slower is red (darker = further from target). Switching between H/s and ms converts the target and relabels the aggregation options automatically.
Early Android data is millisecond-precision only; newer Android builds and
GPU runs include a nanosecond-precision field. Integer-only cells
(e.g. 5) could be anywhere in [5, 6) ms; cells
with decimals have sub-ms precision. When both precisions exist for a cell
the ns value is used. Sub-ms cells with no ns data show as
<1.