Benchmarks
Below we record benchmarks for common operations. Each benchmark verifies its operations in a single-table constraint system. All executions are parameterized for 100 bits of security using proven—not conjectured—proximity gap results. We use a Reed–Solomon rate of .
We report proving times below in the form <TRACE GEN TIME> + <PROVE TIME>
, the sum of the witness-generation time and the SNARK-proving time. We report these times separately because, in many cases, we have not yet parallelized the trace generation.
All performance numbers are collected on an AWS c7i.4xlarge
machine, which has an Intel Sapphire Rapids processor with 16 virtual CPU cores and 32 GiB RAM. All binaries are compiled and run in release mode, with the environment variable RUSTFLAGS="-C target-cpu=native"
.
Hashing
In each hashing-based benchmark, we run enough operations to hash at least 1 MiB of data1.
Operation | Count | Prove (s) | Verify (ms) | Proof Size (KiB) |
---|---|---|---|---|
Keccak- | 213 permutations | 0.20 + 2.98 | 221 | 582 |
Grøstl | 214 permutations | 0.14 + 0.89 | 1,090 | 1,174 |
Vision Mark-32 | 214 permutations | 0.36 + 3.80 | 77 | 1,020 |
SHA-256 | 214 compressions | 0.17 + 4.26 | 508 | 773 |
Integer Operations
Operation | Count | Prove (s) | Verify (ms) | Proof Size (KiB) |
---|---|---|---|---|
u32 add | 4,000,000 | 0.03 + 1.58 | 12 | 529 |
u32 mul2 | 1,000,000 | 1.85 + 11.73 | 41 | 819 |
u32 xor | 4,000,000 | 0.02 + 0.46 | 13 | 488 |
u32 and | 4,000,000 | 0.02 + 0.94 | 12 | 515 |
u32 or | 4,000,000 | 0.02 + 0.96 | 11 | 515 |
Footnotes
-
The procedure by which we determine the number of operations required to hash 1 MiB of data varies depending on the structure of the hash function. Keccak and Vision use a sponge construction, meaning that each Keccak- or Vision permutation absorbs a number of bytes equal to the sponge function's rate. For SHA-3 and Keccak-256, used in Ethereum, the rate is 136 bytes, meaning one Keccak- permutation absorbs 136 bytes of input data. SHA2 and Grøstl use Merkle–Damgård and (closely related) wide-pipe constructions, respectively, based on underlying compression functions. In both hash functions, each compression function processes one 64-byte message block. In the case of Grøstl, we've actually only implemented that hash function's permutation thus far. To process a 64-byte block of input data, that hash function must apply exactly once and exactly once. We thus fudge slightly, by acting as if we were doing both s and s, even though we are actually doing doubly many s. Thus, we count 64 bytes "hashed" for every two executions of the permutation. In each Keccak- benchmark, we run permutations; in each of the others, we run . ↩
-
This is a first attempt at u32 multiplication using decomposition into 8-bit limbs and Lasso lookups for 8-bit multiplications. We are working on implementing a better multiplication technique explained in this post. ↩