Compares SwiftSync OutPoint aggregator designs on two historical mainnet blocks: sums of 128-bit tags from keyed SHA256, AES-CMAC and SipHash128, and GF(2²⁵⁶) products. See why these designs for more details.
Hardware and software backends are benchmarked in separate builds.
| Build | SHA256 | AES-CMAC | SipHash128 | GF256 |
|---|---|---|---|---|
| Native on ARM64 | ARM SHA2 if available, otherwise software | Hardware ARM AES | Scalar + NEON SIMD/interleaved | Hardware PMULL |
| Native on x86-64 | SHA-NI if available, otherwise software | Hardware AES-NI | Portable scalar code | Hardware PCLMULQDQ |
| Software on any CPU | Portable rust-bitcoin implementation | RustCrypto software AES | Scalar + NEON SIMD/interleaved on ARM64 | Portable optimized kernel |
Results for block 866,342, shown as median time (µs) per block. The charts use different vertical scales, and SipHash128 implementation stays the same.
See the full benchmark tables for exact timings and relative time multipliers, and what is timed for the methodology.
You need Rust 1.87 or newer and a C compiler for zstd. Run the commands below from the repository root.
Requires both AES and carry-less multiplication instructions. Native binaries must only run on CPUs with those features.
Enable AES at compile time so RustCrypto can inline its hardware code. The flag works on ARM64 and x86-64. It is optional on Apple Silicon, where AES is already enabled by default.
RUSTFLAGS='-C target-feature=+aes' cargo test
RUSTFLAGS='-C target-feature=+aes' cargo run --releaseIn PowerShell:
$env:RUSTFLAGS = '-C target-feature=+aes'
cargo test
cargo run --release
Remove-Item Env:RUSTFLAGSWorks on CPUs without those instructions, and forces software even on CPUs that have them:
RUSTFLAGS='--cfg aes_backend="soft"' cargo test --no-default-features --features software
RUSTFLAGS='--cfg aes_backend="soft"' cargo run --release --no-default-features --features softwareAll three settings are required:
--no-default-featuresdisables SHA256 hardware dispatch.--features softwareselects software GF256 and CMAC.RUSTFLAGSforces software AES.
AES uses RustCrypto's constant-time implementation. ARM64 SipHash retains NEON.
GF256 uses bit-hole integer multiplication, Karatsuba and shift/XOR reduction, without carry-less multiplication instructions. It avoids secret-dependent branches and lookups, assuming constant-time integer multiplication on the host CPU.
Each run prints its backends. Append -- --quick for a short check.
Tests verify:
- GF256 against independent polynomial vectors and the bit-at-a-time reference.
- CMAC and SHA256 against OpenSSL vectors.
- SipHash against
siphasher. - Software builds disable SHA256 and GF256 hardware dispatch. The runner also verifies that AES acceleration is disabled.
Each case visits every non-coinbase input and every UTXO-eligible output (OP_RETURN and scripts over 10,000 bytes are excluded). The bundled compressed blocks are copies of Floresta's block_367891/raw.zst and block_866342/raw.zst.
| Mainnet block | Inputs | Eligible outputs | Character |
|---|---|---|---|
| 367,891 | 20,817 | 852 | Input-heavy |
| 866,342 | 7,972 | 6,356 | More output work and txid reuse |
Timing includes fingerprinting, aggregator updates and the final merge. Decompression, parsing, txid calculation, output filtering and session-key setup are excluded.
Results report median microseconds per block across 11 samples (each one taking ~100 ms). ns/record divides this by the total input and eligible-output count.
| Case | Timed strategy |
|---|---|
| Keyed SHA256 | First 128 bits of SHA256(txid || vout_le || secret16), one compression per record |
| AES-CMAC | Fixed 36-byte message, reusing the txid work across a transaction's outputs |
| AES-CMAC 8-way | Eight independent records via RustCrypto, with the same txid reuse |
| SipHash128 scalar | Fixed 36-byte scalar kernel with the same txid reuse |
| SipHash128 NEON (ARM64 only) | Four hashes per batch, using two interleaved two-lane NEON vector states and the same txid reuse |
| GF256 direct | Two interleaved product chains, computing each r·vout |
| GF256 common-vout | Four or eight chains with a 0–255 vout table and direct fallback |
| GF256 fast 0/1 | Four or eight chains with precomputed vouts 0 and 1 and direct fallback |
| GF256 bit-at-a-time (software build) | Independent reference using the direct/two-chain strategy |
CMAC and NEON input batches can span transactions. Outputs reuse scalar txid prefixes, then batch their vout finalizations. Batch preparation and scalar tails are timed. CMAC uses RustCrypto, except for the original ARM hardware case's specialized kernel.
GF256 chains are independent partial products interleaved in one thread and merged at the end. They are not SIMD lanes or worker threads.
| Design | Why consider it? | False-pass bound |
|---|---|---|
| Keyed SHA256 | Familiar hardware-accelerated reference, already used in consensus | 2⁻¹²⁸ in the ideal-tag model, assuming secret-suffix SHA256 behaves as a PRF |
| AES-CMAC | Standardized MAC, fast with hardware AES | Same ideal bound, plus the CMAC PRF assumption |
| SipHash128 | Small, portable, with an ARM SIMD option | Same ideal bound, plus the SipHash PRF assumption |
| GF256 | Statistical check without a keyed-function assumption | N / 2²⁵⁶, or 2⁻²²⁴ at N = 2³² |
The additive designs' ideal bound assumes independent uniform 128-bit tags for distinct OutPoints. Their two widened sums cannot overflow with up to 2⁶⁴ records per side, including repetitions. Applying this model to a keyed function requires a PRF assumption: without the key, its outputs behave like those of a random function. The SHA256 case uses a secret suffix, not HMAC.
N is the larger count of added or removed OutPoints. GF256's bound follows from a nonzero polynomial evaluated at independent, uniformly random secrets z and r. It uses the same random-product multiset-check idea described for PLONK.
All bounds require fresh private secrets and records chosen independently of them. Benchmark keys are fixed only for timing.
Background: SwiftSync proposal, CMAC specification, SipHash reference, and PLONK's two-challenge product proof (Appendix A, Claim A.1).

