Monocypher is an easy to use crypto library inspired by libsodium and TweetNaCl. Here are its main selling points:
Secure. Monocypher uses state of the art primitives (Chacha20, Poly1305, Blake2b, Argon2i, X25519, and EdDSA), and is immune to timing attacks.
Small. Monocypher takes less than 1300 lines of code —small enough to allow comprehensive audits.
Easy to deploy. Just put
monocypher.hin your project. They compile as C99, C11, or C++, and have zero dependency.
Easy to use. The API is small, consistent, and cannot fail on correct input.
Fast. The chosen primitives are fast to begin with, and performance wasn't needlessly sacrificed. Monocypher holds up pretty well against libsodium, and is much faster than TweetNaCl.
The Mighty Manual.
Here, shipped with a comprehensive test suite. Alternatively, you can fetch it from github.
the test suite. Run thus:
$ tar -xzf monocypher-0.8.tar.gz $ cd monocypher $ make $ ./test.sh
You can modify the makefile to run those tests under LLVM sanitisers (ASan, MSan, and UBSan). You can also check code coverage (which is near-perfect).
More thorough analysis is also possible with
Frama-C and the
TIS interpreter. See
README.md for more details.
By default, Monocypher signatures use EdDSA with curve25519 and Blake2b. This is in contrast to the more widespread Ed25519, which uses curve25519 and SHA-512.
Blake2b is faster, more flexible, and harder to misuse than SHA-512. Besides, Argon2i already uses it. Adding SHA-512 just for EdDSA would be inelegant, so I didn't —not by default anyway.
This divergence doesn't prevent future upgrades, nor rigorous testing: Floodyberry's very fast Donna implementation works with a custom hash, and this is used to test Monocypher.
If you want to stick to the official Ed25519 anyway, you can. You
need to compile
monocypher.c with the preprocessor option
-DED25519_SHA512. You must then provide a SHA-512 implementation at
link time. You can use Monocypher's own
Version 1.0. Ready for production.
The test suite is comprehensive. Code coverage has been checked with LLVM's code coverage tool, and is is near perfect. The tests have been run under Valgrind, ASan, MSan, and UBSan (all clean). The most important tests have been run under the TIS interpreter (all clean).
I think we have a crypto library.
The last benchmark showed this on my machine (i5 Skylake laptop).
Monocypher, Libsodium, and TweetNaCl are all compiled with GCC, using
-O3 -march=native optimisation options.
Comparing with Libsodium ------------------------ Chacha20 : 2971 micro-secs, 14% slower than Libsodium Poly1305 : 1099 micro-secs, 19% slower than Libsodium Blake2b : 2017 micro-secs, 20% slower than Libsodium Argon2i : 1911 micro-secs, 29% faster than Libsodium x25519 : 127 micro-secs, 59% slower than Libsodium ed25519(sig): 144 micro-secs, 64% slower than Libsodium ed25519(chk): 279 micro-secs, 49% slower than Libsodium
(Note: the timings are for a 1 megabyte input for symmetric crypto, and a single operation for public key crypto. The timings were obtained by taking the best of 20 runs in each case. They rarely vary by more than 1%)
Symmetric crypto is between 15% to 20% slower. It appears to be CPU bound, and libsodium uses assembly routines (or intrinsics) against which Monocypher cannot compete. The gap should be smaller or non-existent on platforms where libsodium doesn't provide assembly routines.
Argon2i appears to be notably faster. This was unexpected. I haven't looked in detail, but for now I suspect inefficiencies in Libsodium. A word of warning, though: Argon2 is very sensitive to optimisation options. It's easy to make an unfair comparison by accident.
Diffie-Hellman and signature verification are about twice as slow, I believe for the same reasons as symmetric crypto (can't compete with assembly).
Signatures are about three times as slow. Some of it comes from portable C vs assembly, but I have to blame my design choices as well: the ref10 implementation uses a huge pre-computed table to speed up scalar product with the base point. To avoid the bloat, I refused to include it in Monocypher, and have to compute the scalar product the hard way.
The comparison with TweetNaCl is simple: Monocypher is just faster.
Comparing with TweetNaCl (apple to orange comparisons are marked with a '!') ------------------------ ! Chacha20 : 2970 micro-secs, 44% faster than TweetNaCl's Salsa20 Poly1305 : 1098 micro-secs, 1010% faster than TweetNaCl ! Blake2b : 2017 micro-secs, 130% faster than TweetNaCl's Sha512 Sha512 : 3489 micro-secs, 34% faster than TweetNaCl x25519 : 127 micro-secs, 349% faster than TweetNaCl ed25519(sig): 144 micro-secs, 976% faster than TweetNaCl ed25519(chk): 279 micro-secs, 1007% faster than TweetNaCl Note: TweetNaCl rejected its own signature, I don't know why
Authenticated encryption is more than 3.5 times faster than TweetNaCl. The discrepancy is even wider under
-O2—TweetNaCl is very sensitive to optimisation options.
Blake2b is faster than SHA-512, as expected.
Public key cryptography is much faster than TweetNaCl, thanks to ref10 arithmetic and performing all scalar multiplications in Montgomery space.
Overall, I think Monocypher strikes a rather nice balance between frugality and performance.
changes since 1.0
- Optimised the loading and unloading code of the symmetric crypto (Blake2b, sha512, Chacha20, and Poly1305). Symmetric crypto performance is now comparable with Libsodium's.
- Fused self contained tests together for easier analysis with Frama-C and the TIS interpreter.
changes since 0.8
crypto_chacha20_x_init, for consistency reasons (snake case everywhere).
- Fixed signed integer overflow detected by UBSan.
- Doubled the speed of EdDSA by performing the scalar product in Montgomery space.
changes since 0.7
Added about a hundred lines of code to improve performance of public key cryptography. Diffie-Hellman is now 20% faster than before. (The effects are less pronounces for EdDSA).
Added random self-consistency tests.
Added a speed benchmark against libsodium. Requires POSIX timers. You might want to adjust the compilation option to make it work (see the beginning of the makefile). To run the benchmark, run the following:
$ make speed $ ./speed
changes since 0.6
- Slightly changed the authenticated encryption API. Functions are now all in "detached" mode. The reason is better support for authenticated encryption without additional data.
- Rewrote Blake2b from spec, so it can use the same licence as everything else.
- Added random tests that compare Monocypher with libsodium and ed25519-donna.
- Added explicit support for Frama-C analysis (this doesn't affect the source code)
changes since 0.5
- Fixed incorrect poly1305 output on empty messages. (Found by Mike Pechkin.)
changes since 0.4
- Fixed many undefined behaviours in curve25519, that occur whenever we perform a left shift on a signed negative integer. It doesn't affect the generated code, but you never know. (Found with Frama-C by André Maroneze.)
Fun fact: TweetNaCl and ref10 have the same bug. Libsodium have corrected the issue, though.
For those who don't comprehend the magnitude of this madness (I didn't
until 2 hours ago), yes, the expression
-1 << 3 is undefined in C.
This is explained in section 6.5.7(§4) of the C11 standard.
changes since 0.3
Fixed critical bug causing Argon2i to fail whenever it uses more than 512 blocks. It was reading uninitialised memory, and the results were incorrect. (Found by Mike Pechkin.)
Fixed an undefined behaviour in curve25519 (
fe_tobytes()). It was accessing uninitialised memory, before throwing it away. It didn't affect the compiled code nor the results, but you never know. (Found with Frama-C by André Maroneze.)
changes since 0.2
No incompatible change.
Got the invariants of poly1305 right, put them in the comments. There was no bug, but that was lucky (turned out the IETF test vectors were designed to trigger the bugs I was afraid of).
Simplified poly1305 finalisation (replaced conditional subtraction by a carry propagation).
- Made a few cosmetic changes here and there.
changes since 0.1
- Public interface significantly reworked. Removed redundant, hard to mess up constructions.
- Added AEAD.
- Sped up curve25519 by a factor of more than 6 (switched to ref10 arithmetic)
- Added various test vectors, completed the consistency tests.
I think I'm pretty much done.
The next thing to do, I believe, are bindings to other languages. Except for Rust. I believe Monocypher should be ported to Rust (as in, re-written), so it can benefit from its security guarantees.