Sorry to be a buzzkill, but that sounds like a recipe for disaster.
Sorry to be a buzzkill, but that sounds like a recipe for disaster.
First, as some have noted, serious crypto primitive implementations are written in assembly. This is both to achieve state-of-the-art performance as well as data-independent execution times. The latter is important to prevent timing attacks.
Second: I'm not sure if this was your point, but some have invoked Heartbleed and other native code disasters. But the kind of problems that lead to Heartbleed aren't likely to be a problem in low-level crypto implementations. This is because they tend to operate on fixed-size buffers using algorithms with little or no conditional logic. While there could certainly be mathematical flaws (i.e. producing the wrong output), something like a buffer overrun is not likely here.
If you look in basically any crypto library, you will find important primitives implemented in assembly. This is even true in the main Go repository, where AES is implemented in assembly.
The difference is that the higher-level crypto is written in Go, not in C; Go is memory safe, much more strongly-typed in general, and with run-time bounds checking which eliminate buffer overflows.
The bugs are almost never in the low-level algorithms, they are in the higher-level components.
Edit: that does indeed appear to be what they've done. In particular using the AESENC instruction. https://github.com/cloudflare/go/blob/master/src/crypto/aes/...
Vlad Krasnov is also a co-author of this paper on state-of-the-art P256 implementation: https://eprint.iacr.org/2013/816.pdf.
JITters are especially bad for this - what is data-independent today may not be data-independent tomorrow. Or even in a couple minutes when it decides to re-optimize.
You ultimately have to dip down to assembly, or something that can be relied on to not do data-dependent optimizations, to ensure resilience against timing attacks.
JNI can work, as can inline assembly in things like C / C++, or specifying compilers. But that's just punting things to another language. And you lose portability, among other things. Or worse, you end up with something that looks like language X, and is valid code in language X, but breaks evilly if it's ever run as though it was in language X.