last benchmark update: 29/01/21
There are several pairing-friendly elliptic curves libraries used in zero-knowledge proofs (ZKP) projects. Typically, the most important elliptic curve operations in ZKP schemes are "multi scalar multiplication" (MSM) and "pairing product" (PP). This writeup is a try to benchmark and compare the building blocks of these operations in two different architechtures.
In fact, since MSM and PP are size-dependant, we focus on timings of mixed addition in G1/G2, doubling in G1/G2, Miller loop and Final exponentiation.
The libraries are written in different languages with different software and mathematical optimizations, and with more or less assembly code.
The target curves are: BN254
, BLS12-381
, BLS12-377
and BW6-761
.
The chosen libraries are:
library | benchmarked curves | original authors | programming language | license | zkp/blockahin project |
---|---|---|---|---|---|
gnark-crypto | BN254, BLS12-381, BLS12-377, BW6-761 |
Consensys | Go | Apache-2.0 | gnark |
kilic/<curve> | BN254, BLS12-381, BLS12-377, BW6-761 |
Onur Kilic | Go | Apache-2.0 | Geth (BLS12-381 ) |
geth/crypto/<curve> | BN254, BLS12-381 |
go-ethereum | Go | LGPL-3.0 | Geth |
arkworks-rs/curves (fromerly zexe) |
BN254, BLS12-381, BLS12-377, BW6-761 |
arkworks-rs | Rust* | MIT/Apache-2.0 | Celo, Aleo (BLS12-377, BW6-761 ) |
celo-org/zexe (based on arkworks) |
BW6-761 |
celo-org | Rust** | MIT/Apache-2.0 | Celo (BW6-761 ) |
zkcrypto/bls12_381 | BLS12-381 |
zkcrypto | Rust | MIT/Apache-2.0 | zcash, Dusk |
mcl | BN254, BLS12-381 |
MITSUNARI Shigeo | C++ | modified BSD-3-Clause | loopring (BN254 ), DFINITY (BLS12-381 ) |
blst | BLS12-381 |
Supranational | C | Apache-2.0 | filecoin |
relic | BLS12-381 |
Diego F. Aranha | C | Apache-2.0/LGPL-2.1 | Chia |
libff (develop branch) |
BN254, BLS12-381 |
SCIPR Lab | C++ | MIT | libsnark |
zk-swap-libff (based on libff) |
BLS12-377, BW6-761 |
EYBlockchain | C++ | MIT | zk-swap-libsnark |
constantine | BN254, BLS12-381, BLS12-377 |
Mamy Ratsimbazafy | Nim | MIT/Apache-2.0 |
* assembly is disabled by default in arkworks-rs
. To enable asm in benchmarks, e.g.: RUSTFLAGS="-C target -feature=+bmi2,+adx" cargo +nightly bench bls12_381::full_pairing --features asm
.
** assembly is disabled by default in celo-org/zexe
. To enable asm in benchmark on BW6-761
curve: RUSTFLAGS="-C target-feature=+bmi2,+adx" cargo +nightly bench pairing --features "force_bw6_asm bw6_761"
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
mcl (BN_SNARK1) | C++ | 0.4765 | 0.2137 | 0.2628 |
gnark-crypto(bn254) | Go | 0.4890 | 0.2321 | 0.2569 |
constantine (BN254_Snarks) | Nim | 0.6471 | 0.2934 | 0.3424 |
kilic/bn254 | Go | 0.8454 | 0.3990 | 0.4464 |
arkworks(+asm) (bn254) | Rust | 1.0655 | 0.4597 | 0.6058 |
arkworks(bn254) | Rust | 1.1915 | 0.5265 | 0.6649 |
geth/cloudflare(bn256) | Go | 1.3013 | 0.4874 | 0.8139 |
libff(alt_bn128) | C++ | 2.1000 | 0.6500 | 1.4500 |
geth/google(bn256) | Go | 11.4304 | 4.1681 | 7.2622 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
mcl (BN_SNARK1) | C++ | 216 | 158 |
gnark-crypto(bn254) | Go | 231 | 143 |
constantine (BN254_Snarks) | Nim | 237 | 151 |
arkworks(bn254) | Rust | 315 | 228 |
kilic/bn254 | Go | 413 | 205 |
geth/cloudflare(bn256) | Go | 472 | 253 |
libff(alt_bn128) | C++ | 4236 | 2800 |
geth/google(bn256) | Go | 8196 | 4102 |
arkworks(+asm) (bn254) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
mcl (BN_SNARK1) | C++ | 594 | 374 |
gnark-crypto(bn254) | Go | 605 | 362 |
constantine (BN254_Snarks) | Nim | 1035 | 481 |
arkworks(bn254) | Rust | 1192 | 732 |
kilic/bn254 | Go | 1378 | 593 |
geth/cloudflare(bn256) | Go | 1738 | 732 |
libff(alt_bn128) | C++ | 6508 | 4533 |
geth/google(bn256) | Go | 19549 | 9370 |
arkworks(+asm) (bn254) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
gnark-crypto (bn254) | Go | 0.5889 | 0.2795 | 0.3094 |
mcl(BN_SNARK1) | C++ | 0.6085 | 0.3375 | 0.2710 |
constantine (BN254_Snarks) | Nim | 0.9029 | 0.4100 | 0.4772 |
kilic/bn254 | Go | 1.0362 | 0.5063 | 0.5299 |
arkworks(+asm) (bn254) | Rust | 1.3271 | 0.5583 | 0.7688 |
arkworks(bn254) | Rust | 1.3834 | 0.6201 | 0.7633 |
geth/cloudflare(bn256) | Go | 1.4720 | 0.5569 | 0.9150 |
libff(alt_bn128) | C++ | 2.800 | 1.300 | 1.500 |
geth/google(bn256) | Go | 13.3843 | 4.7476 | 8.63662 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
mcl (BN_SNARK1) | C++ | 264 | 209 |
gnark-crypto(bn254) | Go | 294 | 173 |
constantine (BN254_Snarks) | Nim | 349 | 224 |
arkworks(bn254) | Rust | 380 | 297 |
kilic/bn254 | Go | 495 | 258 |
geth/cloudflare(bn256) | Go | 514 | 290 |
arkworks(+asm) (bn254) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto(bn254) | Go | 696 | 425 |
mcl (BN_SNARK1) | C++ | 735 | 461 |
arkworks(bn254) | Rust | 1259 | 820 |
constantine (BN254_Snarks) | Nim | 1454 | 691 |
kilic/bn254 | Go | 1736 | 781 |
geth/cloudflare(bn256) | Go | 1912 | 841 |
arkworks(+asm) (bn254) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
blst (BLS12-381) | C | 0.6116 | 0.2642 | 0.3474 |
mcl (BLS12-381) | C++ | 0.6794 | 0.2864 | 0.3930 |
gnark-crypto(bls12-381) | Go | 0.7078 | 0.3320 | 0.3758 |
kilic/bls12-381 | Go | 0.8594 | 0.3690 | 0.4903 |
constantine (BLS12_381) | Nim | 0.9062 | 0.3561 | 0.5210 |
relic (gmp-pbc-bls381 preset) | C | 1.3149 | 0.5780 | 0.7369 |
arkworks(+asm) (bls12_381) | Rust | 1.3271 | 0.5583 | 0.7688 |
geth/bls12381(bls12381, based on kilic) | Go | 1.3494 | 1.5528 | 0.7965 |
zkcrypto/bls12_381 | Rust | 1.5886 | 0.4971 | 1.0914 |
arkworks(bls12_381) | Rust | 1.6218 | 0.6931 | 0.9287 |
libff(bls12_381) | C++ | 3.9000 | 1.5000 | 2.4000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto(bls12-381) | Go | 387 | 245 |
constantine (BLS12_381) | Nim | 416 | 269 |
mcl (BLS12-381) | C++ | 421 | 309 |
blst (BLS12-381) | C | 474 | 287 |
kilic/bls12-381 | Go | 558 | 282 |
arkworks(bls12_381) | Rust | 672 | 414 |
zkcrypto/bls12_381 | Rust | 787 | 565 |
geth/bls12381(bls12381, based on kilic) | Go | 789 | 383 |
relic (gmp-pbc-bls381 preset) | C | 938 | 674 |
libff(bls12_381) | C++ | 4183 | 3001 |
arkworks(+asm) (bls12_381) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto(bls12-381) | Go | 1123 | 681 |
mcl (BLS12-381) | C++ | 1164 | 728 |
blst (BLS12-381) | C | 1293 | 674 |
constantine (BLS12_381) | Nim | 1535 | 781 |
kilic/bls12-381 | Go | 1846 | 791 |
relic (gmp-pbc-bls381 preset) | C | 2126 | 1465 |
arkworks(bls12_381) | Rust | 2143 | 1302 |
geth/bls12381(bls12381, based on kilic) | Go | 2537 | 1052 |
zkcrypto/bls12_381 | Rust | 3071 | 2022 |
libff(bls12_381) | C++ | 7728 | 5361 |
arkworks(+asm) (bls12_381) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
blst (BLS12-381) | C | 0.7474 | 0.3230 | 0.4253 |
mcl (BLS12-381) | C++ | 0.8437 | 0.3508 | 0.4929 |
gnark-crypto(bls12-381) | Go | 0.8742 | 0.4188 | 0.4554 |
kilic/bls12-381 | Go | 1.0757 | 0.4568 | 0.6189 |
constantine (BLS12_381) | Nim | 1.3542 | 0.5425 | 0.7657 |
relic (gmp-pbc-bls381 preset) | C | 1.5581 | 0.8760 | 0.6821 |
arkworks(+asm) (bls12_381) | Rust | 1.5815 | 0.6459 | 0.9356 |
geth/bls12381(bls12381, based on kilic) | Go | 1.6970 | 0.7044 | 0.9926 |
arkworks(bls12_381) | Rust | 1.8994 | 0.8014 | 1.0980 |
zkcrypto/bls12_381 | Rust | 1.9324 | 0.7978 | 1.1346 |
libff(bls12_381) | C++ | 4.6000 | 1.7000 | 2.9000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto(bls12-381) | Go | 511 | 302 |
mcl (BLS12-381) | C++ | 544 | 401 |
blst (BLS12-381) | C | 623 | 345 |
kilic/bls12-381 | Go | 683 | 348 |
constantine (BLS12_381) | Nim | 692 | 438 |
arkworks(bls12_381) | Rust | 750 | 547 |
zkcrypto/bls12_381 | Rust | 933 | 691 |
geth/bls12381(bls12381, based on kilic) | Go | 973 | 463 |
relic (gmp-pbc-bls381 preset) | C | 1099 | 803 |
arkworks(+asm) (bls12_381) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto(bls12-381) | Go | 1410 | 847 |
mcl (BLS12-381) | C++ | 1489 | 904 |
blst (BLS12-381) | C | 1621 | 816 |
kilic/bls12-381 | Go | 2305 | 1003 |
arkworks(bls12_381) | Rust | 2412 | 1516 |
relic (gmp-pbc-bls381 preset) | C | 2515 | 1750 |
constantine (BLS12_381) | Nim | 2543 | 1261 |
geth/bls12381(bls12381, based on kilic) | Go | 2305 | 1003 |
zkcrypto/bls12_381 | Rust | 3543 | 2344 |
arkworks(+asm) (bls12_381) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
gnark-crypto (bls12-377) | Go | 0.9184 | 0.4080 | 0.5104 |
kilic/bls12-377 | Go | 0.9935 | 0.4220 | 0.5715 |
constantine(BLS12_377) | Nim | 1.1160 | 0.4430 | 0.6467 |
arkworks(+asm)(bls12_377) | Rust | 1.5775 | 0.6550 | 0.9225 |
arkworks(bls12_377) | Rust | 1.8769 | 1.3463 | 1.0855 |
EYBlockchain/libff(bls12_377) | C++ | 3.8000 | 1.4000 | 2.4000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto (bls12-377) | Go | 389 | 244 |
constantine(BLS12_377) | Nim | 396 | 269 |
kilic/bls12-377 | Go | 557 | 283 |
arkworks(bls12_377) | Rust | 767 | 410 |
EYBlockchain/libff(bls12_377) | C++ | 4136 | 3071 |
arkworks(+asm)(bls12_377) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto (bls12-377) | Go | 1424 | 883 |
constantine(BLS12_377) | Nim | 1877 | 1104 |
kilic/bls12-377 | Go | 2109 | 909 |
arkworks(bls12_377) | Rust | 2723 | 1635 |
EYBlockchain/libff(bls12_377) | C++ | 7674 | 5284 |
arkworks(+asm)(bls12_377) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
gnark-crypto (bls12-377) | Go | 1.1381 | 0.5104 | 0.6277 |
kilic/bls12-377 | Go | 1.2539 | 0.5245 | 0.7229 |
constantine(BLS12_377) | Nim | 1.6415 | 0.9398 | 0.65337 |
arkworks+asm(bls12_377) | Rust | 1.9030 | 0.7747 | 1.1283 |
arkworks(bls12_377) | Rust | 2.2305 | 0.9726 | 1.2579 |
EYBlockchain/libff(bls12_377) | C++ | 4.5000 | 1.6000 | 2.9000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto (bls12-377) | Go | 487 | 301 |
constantine(BLS12_377) | Nim | 664 | 436 |
kilic/bls12-377 | Go | 682 | 344 |
arkworks(bls12_377) | Rust | 761 | 546 |
arkworks(+asm)(bls12_377) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto (bls12-377) | Go | 1770 | 1082 |
kilic/bls12-377 | Go | 2669 | 1164 |
constantine(BLS12_377) | Nim | 2843 | 1632 |
arkworks(bls12_377) | Rust | 3084 | 1908 |
arkworks(+asm)(bls12_377) | Rust | ^ | ^ |
^ benchmarking arkworks group madd/double with assembly feature enabled seems to output wrong timings. |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
kilic/bw6 | Go | 2.8905 | 1.7953 | 1.0952 |
gnark-crypto (bw6-761) | Go | 3.0681 | 1.9453 | 1.1228 |
celo-org/zexe (bw6_761) | Rust | 3.6068 | 1.8210 | 1.7768 |
arkworks+asm (bw6_761) | Rust | 4.9032 | 2.5087 | 2.3946 |
arkworks (bw6_761) | Rust | 5.5040 | 2.8590 | 2.6450 |
EY/libff (bw6_761) | C++ | 6.3000 | 3.5000 | 2.8000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
gnark-crypto (bw6-761) | Go | 1410 | 862 |
celo-org/zexe (bw6_761) | Rust | 1404 | 945 |
arkworks+asm (bw6_761) | Rust | 1883 | 1213 |
kilic/bw6 | Go | 2101 | 9941 |
arkworks (bw6_761) | Rust | 2193 | 1420 |
EY/libff (bw6_761) | C++ | 7413 | 4683 |
with hyperthreading, turbo and frequency scaling disabled.
↓library | language | Full pairing (ms) | Miller loop (ms) | Final exponentiation (ms) |
---|---|---|---|---|
kilic/bw6 | Go | 3.4712 | 2.2101 | 1.2611 |
gnark-crypto (bw6-761) | Go | 3.5284 | 2.0552 | 1.4731 |
celo-org/zexe (bw6_761) | Rust | 4.4603 | 2.2589 | 2.2014 |
arkworks+asm (bw6_761) | Rust | 6.2773 | 3.2008 | 3.0765 |
arkworks (bw6_761) | Rust | 6.8567 | 3.5297 | 3.3270 |
EY/libff (bw6_761) | C++ | 7.6000 | 4.2000 | 3.4000 |
↓library | language | Mixed addition (ns) | doubling (ns) |
---|---|---|---|
celo-org/zexe (bw6_761) | Rust | 1698 | 1156 |
gnark-crypto (bw6-761) | Go | 1881 | 1150 |
arkworks+asm (bw6_761) | Rust | 2363 | 1591 |
kilic/bw6 | Go | 2525 | 1197 |
arkworks (bw6_761) | Rust | 2666 | 1788 |
The libraries are implemented in different languages and some use more assembly code than others. Besides the different algorithmic and software optimizations used across, it should be noted also that some libraries target constant-time implementation for some operations making it de facto slower. However, it can be clear that consensys/gnark-crypto is one of the fastest pairing-friendly elliptic curve libraries to be used in zkp projects with different curves.
I'm part of a research team at ConsenSys. If you are interested in our work (fast finite field arithmetic, elliptic curve pairings, and zero-knowledge proofs), give us a shout.