Fast Rust inside Motoko canisters, without giving up Motoko: native kernels in moxzi alpha.10
Thanks to @infu for this thread, for PR #6443 and for the plugins themselves (now at Neutrinomic/motoko_plugins): the idea of calling native code from Motoko and every baseline measurement below come from @infu. It made us measure something we had been hand-waving about, and then build it. Here is what came out, written so it makes sense even if you have never compiled a canister by hand.
The problem, in a few numbers. Every message a canister handles has an instruction budget (40 billion on an ordinary subnet), and every instruction costs cycles. Cryptography is where Motoko hurts: verifying an Internet Computer certificate (one or two BLS signature checks) costs about 2 billion instructions in Motoko, SHA-256 of 1 MB about 405 million, a secp256k1 signature about 66 million. All perfectly writable in Motoko, and all several times to dozens of times more expensive than the same work done by a mature Rust crate, because crypto lives on low-level tricks (64-bit limb arithmetic, precomputed tables, hand-tuned field multiplication) that Motoko doesn’t give you.
The idea from this thread. Let a Motoko program call fast code written in another language. The plugins @infu showed did that by building the Rust into the compiler. We took the same idea and made it a library feature instead:
- A kernel is a tiny WebAssembly library built from an existing, well-known Rust crate (RustCrypto, dalek, zkcrypto, libcrux…). It ships inside an ordinary mops package.
- The package’s Motoko function keeps its Motoko body. Under stock
moc, or if you don’t opt in, that body runs and your program behaves exactly as before. With moxzi and the opt-in, the compiler calls the kernel instead and links it only into canisters that actually use it. - You opt in from your own
mops.toml, pinning the exact bytes:
toml [kernels] moxzi-hash = "sha256:d9e07eab…"
A dependency can never add native code behind your back, and if the bytes on disk don’t match your pin, the build stops instead of falling back silently.
Same source, two compilers, same answers: the Motoko body doubles as the test oracle, and every kernel is checked against it (and against official test vectors) on every build of moxzi.
What it buys. Instructions per call, compared with the plugins @infu benchmarked earlier in this thread (the “Plugin” column is @infu’s published numbers; we measured our kernels the same way; lower is better):
| Operation | Plugin (this thread) | moxzi kernel |
|---|---|---|
| secp256k1 ECDSA sign | 3.6 M | 1.5 M |
| secp256k1 BIP340 Schnorr sign | 5.1 M | 2.1 M |
| P-256 verify | 12.9 M | 8.4 M |
| Ed25519 verify | 2.3 M | 2.2 M |
| RSA-3072 verify | 9.8 M | 5.0 M |
| ML-DSA-44 sign (post-quantum) | 14.8 M | 9.0 M |
| BLS12-381 pairing check | 100 M | 64 M |
And the one that started it: mo:ic-agent verifies a mainnet certificate in about 0.37 billion instructions with its kernel, against about 2 billion with the Motoko body alone (0.61 vs 3.8 billion with a subnet delegation). SHA-256 of 1 MB drops from 405 million to 76 million. Hashing (SHA-256/512, HMAC, RIPEMD-160, BLAKE3), Base58, Bech32 and Bitcoin script/transaction helpers are there too. Ten kernel packages in all, plus the BLS kernel inside mo:ic-agent.
A side quest from this thread: we also made moxzi’s built-in to_candid encoder faster. On @infu’s CandidX benchmark (1,024 records; the plugin does it in about 352 K instructions), the built-in encoder went from 832 K to 165 K instructions, with output byte-identical to moc’s — no plugin needed.
“Native code in my canister” should make you nervous. Here’s what we did about it.
A kernel shares your canister’s memory: a buggy or malicious one could read or write anything in it. So:
- You choose, by hash. Kernels we build and publish are recognised by their exact bytes; any other kernel stops the build the first time and shows you its source and what it can do until you explicitly accept that exact version.
- Anyone can rebuild them. Each kernel is built from pinned, locked sources with a pinned toolchain, and rebuilds to identical bytes on macOS and Linux (one Docker command checks all of them). The canister’s public metadata lists every kernel it contains, with its hash and source, so a verifier can tell what’s inside a deployed module.
- We attacked our own code. Constant-time code is the classic crypto pitfall: if signing takes longer for some keys than others, the key leaks. On the IC the observable is the instruction count, so we test that every secret-key operation costs exactly the same for every key. That test found four real leaks in our own code, now fixed. Then we went further and scanned the compiled WebAssembly for branches on secret data — and found that the compiler had quietly turned constant-time Rust into branches in two places, invisible to the instruction count but real on machines that run the same code natively. Fixed, and the scan now runs on every build.
- Adversarial review. Independent reviewers went through the RSA, secp256k1, P-256, Ed25519 and BLS code looking for wrong results and leaks, with a failing test required for every claim.
What we have not done yet is an independent expert audit of the hand-written parts (RSA’s arithmetic, the curve tables). Until that happens, please treat those kernels as fast and well-tested, but not audited: fine for experiments, prototypes and verification work, not yet for guarding real value.
Try it. Install moxzi alpha.10, add one of the moxzi-* packages, add the [kernels] pin, and build. The docs page “Native kernels” walks through it with a SHA-256 example that shows the instruction count with and without the kernel. Writing your own kernel from a Rust crate is moxzi kernel build.
If moc ever adopts compute plugins: our design deliberately keeps programs moc-compatible (the Motoko body is always there) and names kernels by package and function, so the two approaches could meet. We’d be glad to line the names and the calling convention up with whatever the Motoko team prefers.
The kernels live in their own public repository, icdevsorg/moxzi-kernels (MIT OR Apache-2.0), maintained by ICDevs.org (a 501c3 non-profit); if they’re useful to you, please consider supporting that work at https://icdevs.org/donation.html. moxzi itself is built by RIVVIR Tech.
Questions, kernel requests and “you’re wrong about X” all welcome.
Thanks to @infu for this thread, for [PR #6443](https://github.com/caffeinelabs/motoko/pull/6443) and for the plugins themselves (now at [Neutrinomic/motoko_plugins](https://github.com/Neutrinomic/motoko_plugins)): the idea of calling native code from Motoko and every baseline measurement below come from @infu. It made us measure something we had been hand-waving about, and then build it. Here is what came out, written so it makes sense even if you have never compiled a canister by hand.
**The problem, in a few numbers.** Every message a canister handles has an instruction budget (40 billion on an ordinary subnet), and every instruction costs cycles. Cryptography is where Motoko hurts: verifying an Internet Computer certificate (one or two BLS signature checks) costs about 2 billion instructions in Motoko, SHA-256 of 1 MB about 405 million, a secp256k1 signature about 66 million. All perfectly writable in Motoko, and all several times to dozens of times more expensive than the same work done by a mature Rust crate, because crypto lives on low-level tricks (64-bit limb arithmetic, precomputed tables, hand-tuned field multiplication) that Motoko doesn’t give you.
**The idea from this thread.** Let a Motoko program call fast code written in another language. The plugins @infu showed did that by building the Rust into the compiler. We took the same idea and made it a library feature instead:
- A **kernel** is a tiny WebAssembly library built from an existing, well-known Rust crate (RustCrypto, dalek, zkcrypto, libcrux…). It ships inside an ordinary mops package.
- The package’s Motoko function **keeps its Motoko body**. Under stock `moc`, or if you don’t opt in, that body runs and your program behaves exactly as before. With moxzi and the opt-in, the compiler calls the kernel instead and links it only into canisters that actually use it.
- You opt in from **your own** `mops.toml`, pinning the exact bytes:
```toml [kernels] moxzi-hash = “sha256:d9e07eab…” ```
A dependency can never add native code behind your back, and if the bytes on disk don’t match your pin, the build stops instead of falling back silently.
Same source, two compilers, same answers: the Motoko body doubles as the test oracle, and every kernel is checked against it (and against official test vectors) on every build of moxzi.
**What it buys.** Instructions per call, compared with the plugins @infu benchmarked earlier in this thread (the “Plugin” column is @infu’s published numbers; we measured our kernels the same way; lower is better):
| Operation | Plugin (this thread) | moxzi kernel |
|—|—:|—![]()
| secp256k1 ECDSA sign | 3.6 M | 1.5 M |
| secp256k1 BIP340 Schnorr sign | 5.1 M | 2.1 M |
| P-256 verify | 12.9 M | 8.4 M |
| Ed25519 verify | 2.3 M | 2.2 M |
| RSA-3072 verify | 9.8 M | 5.0 M |
| ML-DSA-44 sign (post-quantum) | 14.8 M | 9.0 M |
| BLS12-381 pairing check | 100 M | 64 M |
And the one that started it: `mo:ic-agent` verifies a mainnet certificate in **about 0.37 billion instructions** with its kernel, against about 2 billion with the Motoko body alone (0.61 vs 3.8 billion with a subnet delegation). SHA-256 of 1 MB drops from 405 million to 76 million. Hashing (SHA-256/512, HMAC, RIPEMD-160, BLAKE3), Base58, Bech32 and Bitcoin script/transaction helpers are there too. Ten kernel packages in all, plus the BLS kernel inside `mo:ic-agent`.
A side quest from this thread: we also made moxzi’s *built-in* `to_candid` encoder faster. On @infu’s CandidX benchmark (1,024 records; the plugin does it in about 352 K instructions), the built-in encoder went from 832 K to 165 K instructions, with output byte-identical to moc’s — no plugin needed.
**“Native code in my canister” should make you nervous. Here’s what we did about it.**
A kernel shares your canister’s memory: a buggy or malicious one could read or write anything in it. So:
1. **You choose, by hash.** Kernels we build and publish are recognised by their exact bytes; any other kernel stops the build the first time and shows you its source and what it can do until you explicitly accept that exact version.
2. **Anyone can rebuild them.** Each kernel is built from pinned, locked sources with a pinned toolchain, and rebuilds to identical bytes on macOS and Linux (one Docker command checks all of them). The canister’s public metadata lists every kernel it contains, with its hash and source, so a verifier can tell what’s inside a deployed module.
3. **We attacked our own code.** Constant-time code is the classic crypto pitfall: if signing takes longer for some keys than others, the key leaks. On the IC the observable is the instruction count, so we test that every secret-key operation costs exactly the same for every key. That test found four real leaks in our own code, now fixed. Then we went further and scanned the *compiled* WebAssembly for branches on secret data — and found that the compiler had quietly turned constant-time Rust into branches in two places, invisible to the instruction count but real on machines that run the same code natively. Fixed, and the scan now runs on every build.
4. **Adversarial review.** Independent reviewers went through the RSA, secp256k1, P-256, Ed25519 and BLS code looking for wrong results and leaks, with a failing test required for every claim.
What we have *not* done yet is an independent expert audit of the hand-written parts (RSA’s arithmetic, the curve tables). Until that happens, please treat those kernels as fast and well-tested, but not audited: fine for experiments, prototypes and verification work, not yet for guarding real value.
**Try it.** Install moxzi alpha.10, add one of the `moxzi-*` packages, add the `[kernels]` pin, and build. The docs page “Native kernels” walks through it with a SHA-256 example that shows the instruction count with and without the kernel. Writing your own kernel from a Rust crate is `moxzi kernel build`.
**If moc ever adopts compute plugins:** our design deliberately keeps programs moc-compatible (the Motoko body is always there) and names kernels by package and function, so the two approaches could meet. We’d be glad to line the names and the calling convention up with whatever the Motoko team prefers.
The kernels live in their own public repository, [icdevsorg/moxzi-kernels](https://github.com/icdevsorg/moxzi-kernels) (MIT OR Apache-2.0), maintained by ICDevs.org (a 501c3 non-profit); if they’re useful to you, please consider supporting that work at https://icdevs.org/donation.html. moxzi itself is built by RIVVIR Tech.
Questions, kernel requests and “you’re wrong about X” all welcome.