Workload
For a namespace , let map an item to a group element. The digest of a multiset is
D_N(S) = sum of H_N(x), with multiplicity, over x in S.
Insertion and removal add or subtract a hashed point. A prepared representation of is reused for every coded symbol the item maps to. The measured costs are hashing to a reusable addend, signed accumulation, and encoding or decoding accumulated points.
The encoding must represent the additive closure of the hash outputs, including the identity, negatives, and sums, because coded symbols hold arbitrary sums. Prime order is not required. A subgroup or quotient group is sufficient if hashing, equality, accumulation, and canonical encoding all refer to the same group. Not every bit string needs to be a valid encoding.
The RIBLT workload implements encoding and peeling with an XOR item field, an integer count, and an ECMH checksum. It is a cost model for these operations, not a specification of a wire protocol.
Cost in repeated reconciliation
The RIBLT mapping includes an item in coded symbol with probability , so an item enters , about , of the first symbols, and symbol holds about of items: the first symbols are dense. Reconciling a difference of items takes of about symbols [yang-gilad-alizadeh-2024]. Under three assumptions,
- weights are expected weights,
- every round reconciles the same number of new items, and
- each round rebuilds its symbols from the full set,
one round over a set of items costs additions, about . Hashing is per item and its result can be kept, so a set that grows to items by rounds of new items costs hashes in all, but
sum over rounds of n_j k(1.35d), about (N^2 / d) ln(1.35d) additions.
Under this model the number of additions grows quadratically in and the number of hashes linearly: a round performs hashes against additions. Which dominates the elapsed time also depends on their measured unit costs. Decoding adds about subtractions per round, and one equality test per examined symbol, lower order than rebuilding when is much larger than . If instead each party keeps its symbol sums between rounds and adds only new items (assumption 3 dropped), encoding falls to about additions, linear in , with one hash per additions; decoding is then of the same order. Kept hashes and sums are valid only within one namespace: a new beacon value changes the salt, the curve and the mapping, and everything is recomputed.
Comparison with the reference implementations
The Go benchmarks of
yangl1996/riblt#3 run at pinned
revisions of [riblt-ecmh]: the upstream XOR checksum, the ristretto255 ECMH with
the reference 64-bit mapping, and the same with a ChaCha8 mapping. All three are
run by nix run .#riblt-go-bench. riblt.stream follows their
BenchmarkEncodeAndDecode. The decoder holds items of its own and the
common ones, and the encoder holds of its own and the common ones;
symbols are 64 bytes, and insertion is outside the timed routine. riblt.encode
per item corresponds to BenchmarkSketchAddSymbol at smaller . ristretto255
runs as a family of the RIBLT suites, so that a small curve and the fixed group
are measured in one harness.
The implementations differ in ways that affect the comparison:
- Items are hashed with tagged, salted SHA-256 (two digests for ristretto255),
where riblt-ecmh hashes the bare symbol with SHA-512. The upstream XOR
checksum is SipHash-2-4 under a fixed key;
xor-siphash.64keys it with the salt. - The rateless decoder recovers an item with the addend its purity check prepared; the ristretto255 version of riblt-ecmh hashes the item to the curve again when it recovers it.
- The ristretto255 addend is the point itself, since curve25519-dalek does not expose its cached form.
- The Rust default mapping uses xoshiro256++ seeded by the whole 32-byte map
digest. Rows marked
map=chacha8use ChaCha8, andmap=mcg64rows use the reference 64-bit generator. The Go ChaCha8 version of riblt-ecmh replaced that generator because two items with equal 64-bit seeds have equal mappings, and the decoder cannot separate them (yangl1996/riblt#3; Known weaknesses). Even Rust’s ChaCha8 comparison uses different output from Go’s ChaCha8Rand, so their cells differ. An unmarked mapping row uses the default of its recorded source revision; historical runs are not relabelled when the default changes.
The checksum work is hashing items to addends, adding addends to cells, checking
purity while peeling, and encoding or decoding points. Rows that differ only in
the group, in the same Rust harness with the same mapping, workload, batching
and build, measure checksum substitution, including the choice of hash and
point representation. A time ratio against Go riblt-ecmh also includes the
mapping generator, the reuse policies above and the port from Go to Rust;
these implementation gains are not attributed to this project. Replacing
ChaCha8 with xoshiro256++ is of the same kind: it is chosen to make the
checksum’s cost clearer in the end-to-end figures. Matched map=chacha8 rows
provide a comparison of the generator’s effect once the new clean runs are
measured; historical timings do not quantify this change.