A no_std implementation of the Noise Protocol Framework revision 34 in Rust.
snow is the "official" Noise implementation in Rust that is used by most people. Although the implementation is great, the pattern/ciphersuite is passed in a string and most of the validation is done at runtime.
nivalis has the goal to check the validity of the pattern, role, and ciphersuite at compile time. It will also block the compilation if you didn't provided the correct keys for a given pattern using const assertion in the build() method. The goal of that is to make it almost impossible to missuse and potentially panic at runtime. It also allows you to use your IDE's auto-complete to see which patterns/ciphersuites are available.
This isn't a claim that nivalis is more correct than snow cryptographically, it is just a new API which aims to catch setup mistake earlier. The builder uses a typestate pattern where LS, RS, LE, RE, and PSK are bool const generics tracking, at compile time, which pieces have been supplied so far, and P: PatternRequirements<R> ties those flags to what the chosen pattern and role actually need.
Also, with snow, a PSK is supplied with an explicit slot index (e.g. .psk(0, key)), which means it's possible to build something like IKpsk2 while actually passing the key at index 0. nivalis doesn't expose an index at all, .psk(key) takes a single key, and the pattern itself determines where that key gets mixed into the handshake. There's exactly one PSK slot to fill, so there's no wrong slot to fill it at. The drawback of this is that it doesn't allow you to use patterns that use multiple PSKs such as XXpsk0+psk3.
The patterns supported by this implementation are all the one-way, interactive (fundamental), and the PSK variation presented on Noise Explorer.
The full list includes:
- N, K and X
- NN, NK, NX, XN, XK, XX, KN, KK, KX, IN, IK, IX
- Npsk0, Kpsk0, Xpsk1
- NNpsk0, NNpsk2, NKpsk0, NKpsk2, NXpsk2, XNpsk3, XKpsk3, XXpsk3, KNpsk0, KNpsk2, KKpsk0, KKpsk2, KXpsk2, INpsk1, INpsk2, IKpsk1, IKpsk2, IXpsk2
Fallback patterns are not supported yet.
All cipher, dh and hash methods defined by the Noise protocol's documentation can be used.
For Cipher, it includes:
- AES-GCM
- ChaCha20-Poly1305
For DH:
- X25519
- X448
For Hash:
- Blake2b
- Blake2s
- SHA256
- SHA512
This crate has been implemented as no_std-only.
Because nivalis does not assume an OS or runtime environment, it doesn't provide a default source of randomness. Applications must provide an RNG implementation appropriate for their target.
nivalis is tested against, and passes, the full set of published test vectors from both cacophony and snow.
Building an initiator and a responder for the IK pattern:
use nivalis::{
builder::NewBuilder,
crypto::{cipher::chacha20::ChaChaPoly, dh::{DH, DHKeypair, x25519::X25519dh}, hash::blake2s::Blake2s},
patterns::{IK, roles::{Initiator, Responder}},
};
use rand::rng;
let mut rng = rng();
let init_static = X25519dh::generate_keypair(&mut rng);
let resp_static = X25519dh::generate_keypair(&mut rng);
let initiator = NewBuilder::<IK, Initiator, X25519dh, ChaChaPoly, Blake2s>::new()
.local_static_key(init_static.private().to_owned())
.remote_static_key(resp_static.public())
.build()
.expect("failed to build initiator handshake state");
let responder = NewBuilder::<IK, Responder, X25519dh, ChaChaPoly, Blake2s>::new()
.local_static_key(resp_static.private().to_owned())
.build()
.expect("failed to build responder handshake state");If you tried to .build() the initiator above without calling .remote_static_key(...) first, the program would refuse to compile.
See examples/ for a full handshake-to-transport walkthrough, including sending and decrypting an encrypted message once the handshake completes.
This crate's performance is comparable to snow. The benchmarks below (that were adapted from snow's) compare equivalent operations between the two libraries using the same patterns and ciphersuites :
| Benchmark | snow | nivalis |
|---|---|---|
| XX Builder | 23.829 µs | 28.678 µs |
| NN Builder | 238.97 ns | 147.35 ns |
| XX Handshake | 323.08 µs | 316.36 µs |
| NN Handshake | 127.89 µs | 133.59 µs |
| Transport throughput | 1.2163 GiB/s | 1.2284 GiB/s |
These numbers come from a single run on a personal computer, they are only documented to give you an idea of the performances between the two crates.
To test it yourself on your computer, run:
╰─λ cargo benchnivalis has not been independently security-reviewed or audited. It passes the standard Noise test vectors, which gives confidence in protocol-level correctness, but that is not a substitute for cryptographic review. Do not use this in a security-critical context without an audit. Use at your own risk, and please report anything that looks wrong.
This is very much a young project, and there's a lot of room to help. Issues and PRs are welcome, if you're considering a larger change, opening an issue first to discuss the approach is appreciated.