mcl: a pairing based cryptography library with hand written assembly
a portable and fast pairing-based cryptography library
At a glance
- What is it?
- mcl is a C++ library for pairing based cryptography, implementing the optimal Ate pairing over BN curves and BLS12-381 with assembly paths selected at runtime. It is a low level building block rather than a usable crypto library, and its power comes from the same place as its hazards: hand written assembly and JIT generated code.
- Who is it for?
- mcl suits a protocol engineer implementing pairing based schemes who needs the Ate pairing over a specific curve and wants the field arithmetic tuned for their CPU, and it does not suit an application developer who needs signatures or key exchange, since the readme points to a separate project for BLS signatures and a C API document for serialisation and hash to curve.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A field arithmetic library, not a crypto library
mcl is described as a portable and fast pairing based cryptography library, and the abstract says it supports the optimal Ate pairing over BN curves and BLS12-381 curves.
That is the scope, and it is narrower than the phrase pairing based cryptography might suggest. mcl is the arithmetic layer. It gives you the curve, the group operations, the pairing and the serialization primitives, and expects you to build the protocol on top. The readme makes this explicit with a single line: see the bls project if you want mcl for BLS signatures. So if your goal is a signature scheme rather than a pairing, this is the wrong repository and that is the right one.
The C API is documented separately. The readme points to api.md and to the FAQ section inside it for serialization and hash to curve, which is the correct order of reading, because hash to curve is where the compatibility requirements live between implementations.
The primary language is C++, and the samples directory shows the range: pairing.cpp and pairing_c.c for the core operation, ecdh.cpp and eip-196.cpp for key agreement, bls_sig.cpp, vote.cpp, tri-dh.cpp, she_g1only.cpp and she_smpl.cpp for the Secret Key Exchange scheme, plus aggregate_sig_test and sigma_protocol_test in the test sources.
The repository also has an ffi/ directory and a go.mod, with the module declared as github.com/herumi/mcl and go 1.13. So there is a Go binding path as well as the C one, and the C API is the interface that binding wraps.
The curves, and the field size limit that constrains them
The supported curve list is short and specific, and the way the curves are defined is worth reading carefully because it determines which prime you are working over.
A BN curve is given as p(z) = 36z^4 + 36z^3 + 24z^2 + 6z + 1. The concrete curves are BN254 over the 254-bit prime where z is negative (2^62 + 2^55 + 1), BN_SNARK1 over a 254-bit prime with n := p + 1 - t having high 2-adicity, BN381_1 over the 381-bit prime where z is negative (2^94 + 2^76 + 2^72 + 1), and BN462 over the 462-bit prime where z is 2^114 + 2^101 - 2^14 - 1. Then there is BLS12-381 and BLS12-377.
The catch is the field size. The default mcl library supports up to 384 bits for the field Fp over which the elliptic curve is defined, and up to 256 bits for the order field Fr of the elliptic curve, with MCL_FP_BIT=384 and MCL_FR_BIT=256. Both must be at most 384, which is MCL_BINT_MAX_BIT in include/mcl/config.hpp, because the generated low level functions support up to 384-bit multiplication. The consequence is stated plainly: initialization of a curve over a larger prime such as BN462 fails.
So BN462 is in the list of curves the library knows about and is not in the list of curves the default build can initialise. If you need it, the readme gives the procedure: set MCL_BINT_MAX_BIT in include/mcl/config.hpp and BINT_MUL_N, which is MCL_BINT_MAX_BIT divided by 64, in the Makefile to the same size, then regenerate the low level functions with make update_all_asm, which requires LLVM, and make header.
That is a real, documented procedure with a real cost, and it is the kind of thing that makes a library like this pleasant to work with: the limit is stated, the escape hatch is spelled out, and the way out is the same toolchain the library already uses for the assembly.
Hand written assembly selected at runtime
The performance story in mcl is unusual enough that it deserves to be described plainly, because it is also the main source of risk.
The build produces low level functions from LLVM IR sources, src/base64.ll and src/bint64.ll for a 64-bit CPU or the 32-bit variants otherwise. The bint64.ll file is necessary if MCL_BINT_ASM=1, and if MCL_BINT_ASM_X64=1 then a hand written assembly file is used instead, with src/asm/bint-x64-amd64.S on Linux, the mingw variant on MinGW and the win variant on Windows.
The readme says that assembly file is faster than src/bint64.ll because it uses mulx, adox and adcx if the CPU supports them, and that the selection is made at runtime by mcl::bint::getCpuType(). So the library ships a portable path and a tuned path, and decides per machine which to take.
On x64 environments mcl uses JIT code, and there is a way to avoid it. The readme gives the command for a static code library, and the constraint that comes with it: the generated library supports only BLS12_381 and requires the compiler options -DMCL_FP_BIT=384 and -DMCL_STATIC_CODE. A static library that only covers one curve is a narrow thing to deploy, but it is exactly what you want when you cannot rely on the JIT.
The CPU story has a wrinkle worth flagging. Version v4 includes breaking changes, and v4.30 re-adds support for Haswell or older CPUs, but the readme says this is likely to be removed again in roughly a year. BMI2 and ADX are used if the CPU supports them, meaning Broadwell (2014) or later, and MCL_CPU=noadx forces the portable code. Between v4.00 and v4.20, Haswell or older CPUs were not supported at all.
So a build target has a short support window, and the environment variable to force the portable path is documented, which is the important part for anyone who has to run on old hardware.
Building it, and the platforms that need extra work
The build instructions are thorough, and they differ by platform in ways that will cost you time if you do not read them first.
For x86-64 Linux and macOS, the Makefile route is a clone and a make.
git clone https://github.com/herumi/mcl
cd mcl
make -j4The output is lib/libmcl.a as a static library and lib/libmcl.so as a shared library. clang++ is required except on x86-64 Linux and Windows with MinGW, and where it is needed you pass it on the command line.
make -j4 CXX=clang++The CMake route is the same shape.
mkdir build
cd build
cmake ..
makeAnd for other platforms, including MinGW and for Visual Studio, the readme is specific that clang++ is required, which is why the MinGW note says to use clang++ instead of gcc on mingw.
The dependency situation is mild. GMP is necessary only to build the test programs, and OpenMP is optional, enabled with make MCL_USE_OMP=1 to use it for mulVec.
Two more platform notes. On s390x and systemz you need to install llvm and clang and run make UPDATE_ASM=1 once. For 32-bit x86 Linux, you build GMP in 32-bit mode first, with the ABI=32 configure invocation, and then build mcl with make ARCH=x86 LLVM_VER=-21 GMP_DIR pointing at the install directory.
The supported architecture list is longer than the mainstream three: x86-64 Windows with Visual Studio 2022 or later, x86 and x86-64 Linux with gcc or clang, x86-64 and M1 macOS, ARM and ARM64 Linux, s390x, and separately WebAssembly and Android through their own companion repositories, plus iPhone.
Testing, profiling and the shape of the test suite
The test story is part of the library's design rather than an afterthought, and the Makefile shows how much of it there is.
The test target list is long and specific. There are separate sources for fp_test, ec_test, fp_util_test, window_method_test, elgamal_test, fp_tower_test, gmp_test, bn_test, glv_test, paillier_test, she_test, vint_test and conversion_test, and then aggregate_sig_test, array_test and sigma_protocol_test, plus modp_test, ecdsa_test, mul_test, bint_test and low_func_test. There are also curve specific suites that get compiled in only for the relevant field size: bn_c256_test and she_c256_test for the 256 bit configuration, bn_c384_256_test, she_c384_256_test, bbs_test, bls12_test and mapto_wb19_test for the 384_256 configuration, and bn_c384_test and she_c384_test for the full 384 bit one.
That last point is the key structural detail. The library is built in configurations, with MCL_SUF set to 256, 384_256 or 384 depending on the field and order sizes, and the test sources and the library objects change with it. A curve specific test binary only exists in the configuration that supports that curve.
The tests are built with CMake, using the MCL_BUILD_TESTING option.
cmake .. -DMCL_BUILD_TESTING=ON
make -j4The binaries land in ./bin, and the readme shows how to run a specific one, with a C test and a C++ test for the BLS12-381 pairing.
Profiling is supported in two ways. With perf, the build uses make MCL_USE_PROF=1 and the run is enabled with the MCL_PROF environment variable. With Intel VTune, the same pattern with MCL_USE_PROF=2.
The readme is honest about the Visual Studio path being not maintained and tells you to use the vcxproj file instead, and it notes that clang++ is used for src/base64.ll if it is found, as mklib.bat does, and that without it the library is slower on CPUs without BMI2 and ADX. That is a small detail with a large performance consequence, and it is the kind of thing a readme usually leaves out.
Where mcl is the wrong tool
There are four clear reasons to pick something else, and they are all about what mcl deliberately does not do.
First, it is not a crypto library in the sense most people mean. There is no signature scheme, no key exchange, no AEAD, no KDF. The samples include ecdh.cpp and eip-196.cpp, and the she_ files implement the Secret Key Exchange scheme, but those are demonstrations of what the arithmetic can express rather than a maintained, audited protocol suite. If you need a signature scheme, the readme sends you to the bls project, which is the right structure: mcl is the primitive, and protocol implementations live elsewhere.
Second, the field size limit is a hard architectural boundary in the default build. BN462 is named as a supported curve and cannot be initialised with the default configuration, and getting it to work means regenerating the low level assembly with a specific LLVM toolchain. If your protocol is defined over a large prime, you are signing up to own that build step.
Third, the CPU floor and the JIT. The library dispatches to hand written assembly at runtime, and the assembly path is preferred, with the portable path available through MCL_CPU=noadx. The JIT is the default on x64, and avoiding it means a static code library that only supports BLS12_381. Both of those are reasonable, but they are constraints on where the library can run, and the support window for Haswell or older processors is explicitly short, since the readme says it is likely to be removed again in roughly a year.
Fourth, the build system is a large part of the product. There is a Makefile, a Makefile.cross, a Makefile.wasm, a common.mk, common.props, makefile.win, mk.bat, mklib.bat, mk_arm64.bat, setvar.bat and setvar_arm64.bat, plus CMakeLists.txt and a cmake/ directory. The x64 assembly is checked in while the 32-bit and other variants are generated from LLVM IR, and the s390x and 32-bit x86 paths need separate preparation steps. None of that is a defect, but it does mean that contributing or packaging mcl for a new platform is a larger task than it looks from the readme.
On the alternative, the honest comparison is with the higher level libraries that wrap a pairing implementation. A library that provides a complete signature scheme gives you protocol correctness, serialization compatibility with other implementations, and a threat model someone has thought through, and it hides all of the above. mcl gives you the opposite: raw control over the field arithmetic, the freedom to define your own protocol over the pairing, and performance on the CPU you actually deploy on. For a protocol engineer who knows exactly what they need to build, that is the right trade. For anyone who wants a working signature scheme on their first afternoon, it is not, and the readme says so.
One final note on the licence. The library is BSD-3-Clause, which is permissive and carries no copyleft obligation, so embedding it in a commercial product is straightforward, and the repository carries a COPYRIGHT file along with the source. Any conclusion beyond that needs advice from someone qualified rather than a summary here.
Editorial conclusion
mcl suits a protocol engineer implementing pairing based schemes who needs the Ate pairing over a specific curve and wants the field arithmetic tuned for their CPU, and it does not suit an application developer who needs signatures or key exchange, since the readme points to a separate project for BLS signatures and a C API document for serialisation and hash to curve. Before you adopt it, confirm your curve is in the supported list and that your prime fits the field size limit, because the default build supports up to 384 bits for the field Fp and 256 for the order field Fr, and check the CPU requirement, since v4.00 to v4.20 dropped support for Haswell or older processors.
Frequently asked questions
What is the mcl cryptography library?
mcl is a C++ library for pairing based cryptography, supporting the optimal Ate pairing over BN curves and BLS12-381 curves. It is the field arithmetic layer rather than a complete protocol implementation.
Which curves does mcl support?
The listed curves are BN254, BN_SNARK1, BN381_1, BN462, BLS12-381 and BLS12-377. The default build supports up to 384 bits for the field Fp and 256 bits for the order field Fr, so BN462 over its 462-bit prime fails to initialise without regenerating the low level functions.
How do I build mcl on Linux?
Clone the repository, enter the directory and run make -j4, which produces lib/libmcl.a and lib/libmcl.so. clang++ is required except on x86-64 Linux, and GMP is only needed to build the test programs.
Does mcl need a CPU with BMI2 and ADX?
It prefers them and selects at runtime. BMI2 and ADX are used on Broadwell (2014) or later CPUs, and MCL_CPU=noadx forces the portable code. Version v4.00 to v4.20 did not support Haswell or older CPUs, and v4.30 re-added that support but the readme says it is likely to be removed again in roughly a year.
What licence is mcl released under?
mcl is released under the BSD-3-Clause licence, with the copyright notice in the COPYRIGHT file at the repository root.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/herumi-mcl)