# snow: a Rust implementation of the Noise Protocol Framework

> snow is an Apache-2.0 or MIT licensed Rust crate that implements Trevor Perrin's Noise Protocol Framework, with swappable cryptographic providers, a no_std path and a feature-driven primitive selection model. The judgement is that its clearest selling point is also its most honest warning, since the project states plainly that the library has not received any formal audit.

**mcginty/snow** — A Rust implementation of the Noise Protocol Framework

- Repository: https://github.com/mcginty/snow
- Stars: 1,105 · Forks: 150
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mcginty-snow

## A Noise implementation with an explicit audit disclaimer

snow is an implementation of Trevor Perrin's Noise Protocol, and the README is unusually direct about its posture. The tagline is that it is designed to be hard to fuck up, and then comes the warning in bold: this library has not received any formal audit.

That disclaimer is worth reading as a design statement rather than a hedge. Noise is a protocol framework, not a protocol, so what you get from a library like this is the machinery to run a handshake and the transport that follows it, and the security of your system depends as much on which handshake pattern you choose and on the payload protocol you layer on top as on the library's correctness. An unaudited implementation of the framework is a statement that the library is a careful implementation of a published specification, not a substitute for a review of your protocol design.

The project tracks against Noise spec revision 34, described as the latest Noise Protocol version. All features are implemented with the exception of the `fallback` modifier, for which pull requests are welcome.

The crate is published on crates.io as `snow`, with the API documentation on docs.rs, and the version in the manifest is `0.10.0`. The edition is 2024 with a `rust-version` of 1.85, and the authors are listed as Jake McGinty and trevp, the latter being the author of the Noise specification itself.

The repository has a `SECURITY.md` at the root, and a `hfuzz/` directory for fuzzing, plus `benches/` for benchmarks.

## The handshake is a state machine, and the API mirrors that

The example in the README is the whole API in a few lines, and it shows the shape of the thing clearly.

```rust
let mut noise = snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse()?)
                    .build_initiator()?;

let mut buf = [0u8; 65535];

// write first handshake message
noise.write_message(&[], &mut buf)?;

// receive response message
let incoming = receive_message_from_the_mysterious_ether();
noise.read_message(&incoming, &mut buf)?;

// complete handshake, and transition the state machine into transport mode
let mut noise = noise.into_transport_mode()?;
```

The pattern name is a string, parsed into a type. `Noise_NN_25519_ChaChaPoly_BLAKE2s` encodes the handshake pattern, the Diffie-Hellman function, the cipher and the hash, in one identifier.

`build_initiator()` picks the role. Then the handshake is a sequence of `write_message` and `read_message` calls, each with a payload, which for the first message is empty. The final call, `into_transport_mode()`, is the transition: the handshake is finished and the same object now encrypts and authenticates application data.

That last part is the design that matters. The same `noise` value that was a handshake participant becomes the transport, so there is no second object to wire up and no chance of using the pre-handshake object by mistake. A 65535 byte buffer is used for the messages, and the library does the framing.

The repository has two example files, `examples/simple.rs` for the more complete TCP client and server example the README points to, and `examples/oneway.rs`.

## Crypto providers are chosen at the Builder, not at the call site

The provider model is where snow does something most crypto crates do not.

Cryptographic providers are swappable through `Builder::with_resolver()`, and by default it chooses select, artisanal pure-Rust implementations. The alternative is ring, a crypto library based off BoringSSL, which the documentation notes is significantly faster than most of the pure-Rust implementations.

The feature flags control both availability and default. If you enable the `ring-resolver` feature, Snow includes a `resolvers::ring` module with a `RingAcceleratedResolver` that you can pass to `Builder::with_resolver()`. If you enable the `ring-accelerated` feature, Snow will default to choosing ring's crypto implementations when available.

The resolver primitives table shows the coverage of the two providers. The default resolver supports CSPRNG, 25519, AESGCM, ChaChaPoly, SHA256, SHA512, BLAKE2s, BLAKE2b and BLAKE3. The ring resolver supports CSPRNG, 25519, AESGCM, ChaChaPoly, SHA256 and SHA512, and does not cover 448, P-256, XChaChaPoly, BLAKE2s, BLAKE2b or BLAKE3.

Two entries are marked as not part of the official Noise specification and need to be enabled by hand: P-256 through the `use-p256` flag and XChaChaPoly through `use-xchacha20poly1305`. The `Cargo.toml` explains the naming convention for the feature flags, with a `-resolver` suffix meaning the resolver becomes available and an `-accelerated` suffix meaning it becomes the Builder's default.

The `Cargo.toml` also records why the ring resolver is missing some primitives: ring does not provide BLAKE2 or X25519 with APIs snow can use, so those are enabled separately to ensure any default-supported pattern still works while compiling minimal RustCrypto dependencies otherwise. That is a careful bit of feature engineering, and it explains why the `ring-accelerated` flag pulls in `use-blake2` and `use-curve25519`.

## no_std means picking every primitive yourself

Snow can be used in no_std environments if alloc is provided, and the way to get there is to turn the defaults off.

By default Snow uses the standard library, the default crypto resolver and a selected collection of crypto primitives. To use it in a no_std environment or for other customised setups, you use Snow with `default-features = false`, and then you individually select the components you wish to use. The documentation notes that `default-resolver` is the only built-in resolver that currently supports no_std.

The primitive feature flags are listed in a table. The Diffie-Hellman functions are `use-curve25519` for Curve25519 and `use-p256` for P-256. The ciphers are `use-aes-gcm` for AES-GCM, `use-chacha20poly1305` for ChaChaPoly and `use-xchacha20poly1305` for XChaChaPoly. The hashes are `use-sha2` for both SHA-256 and SHA-512, `use-blake2` for BLAKE2s and BLAKE2b, and `use-blake3` for BLAKE3.

Two example configurations are given, and they show the shape of the decision.

```toml
default-features = false
features = [
    "use-curve25519",
    "use-aes-gcm",
    "use-sha2",
    "std",
]
```

That one is Curve25519 with AES-GCM and SHA-2, with standard library features. The second is the same without the standard library, swapping AES-GCM and SHA-2 for ChaChaPoly and BLAKE2:

```toml
default-features = false
features = [
    "use-curve25519",
    "use-chacha20poly1305",
    "use-blake2",
]
```

One practical note: most crypto implementations supported by `default-resolver` will require getrandom, and if your target platform is not directly supported you might have to provide a custom implementation in your crate root, with the documentation pointing at the getrandom custom implementation docs.

The `default` feature set is `default-resolver`, `default-resolver-crypto` and `std`, and `default-resolver-crypto` pulls in the primitives `use-aes-gcm`, `use-chacha20poly1305`, `use-blake2`, `use-sha2`, `use-curve25519` and `use-getrandom`.

## Two feature flags that exist for compatibility rather than capability

The feature list in the manifest contains two entries that are not about crypto choices, and they are easy to misread.

There is a `nightly` feature, which enables `blake2/simd_opt` and `subtle/nightly`. That is a performance and constant-time-optimisation path for nightly toolchains, and it is opt-in.

Then there are the backwards-compatibility aliases, `pqclean_kyber1024` mapping to `use-pqcrypto-kyber1024`, and `xchachapoly` mapping to `use-xchacha20poly1305`. The `xchachapoly` alias exists because that primitive is spelled differently from the others, and an alias keeps existing build configurations working.

The Kyber alias is the more interesting one, because it points at post-quantum key agreement. The name `pqclean_kyber1024` and the target `use-pqcrypto-kyber1024` are visible in the manifest, but the feature is grouped under backwards-compatibility aliases rather than under the main capability flags, and the README's own tables of DH functions list only Curve25519 and P-256. The reading that fits the evidence is that post-quantum support exists in the dependency set but is not a documented, first-class part of the library's supported patterns, and the alias exists so that configurations written against an earlier naming convention still resolve.

If you need post-quantum key agreement, that is something to check against the actual source and the issue tracker rather than assume from the manifest.

There are three other feature flags that look like test or experiment switches: `vector-tests`, `hfs` and `risky-raw-split`. The naming suggests they are for test-vector verification, for something the manifest does not explain, and for a raw-splitting behaviour the author considers risky. They are not documented in the README, and the `hfuzz` directory at the repository root suggests the fuzzing harness is what `hfs` refers to.

Also worth knowing: the crate declares `license = "Apache-2.0 OR MIT"` and the README spells out the dual licensing and the contribution terms, where any contribution is dual licensed as above without additional terms or conditions, as defined in the Apache-2.0 license.

## Conclusion

snow fits a Rust developer implementing a Noise handshake, who wants a self-contained crate with no async runtime assumptions and who needs to choose between pure-Rust primitives and a faster ring-backed resolver at build time. It does not fit a project that requires a formally audited implementation, since the project says itself that it has not received one, and it does not fit a build that needs post-quantum key agreement as a default, since the Kyber support is present only as a backwards-compatibility alias behind a feature flag. Verify first that the handshake pattern you intend to use is in the supported list, since the only unimplemented part of the specification is the `fallback` modifier, that the primitives you need are enabled by the feature flags you choose, since disabling default features requires selecting each one individually, and that the `fallback` modifier is genuinely not something your deployment needs.

## FAQ

### What is the snow crate and which Noise specification does it implement?

snow is a pure-Rust implementation of Trevor Perrin's Noise Protocol Framework, published on crates.io as `snow` at version 0.10.0. It tracks against Noise spec revision 34, and all features are implemented with the exception of the `fallback` modifier.

### Has the snow crate been audited?

No. The README states in bold that this library has not received any formal audit. The project also ships a `SECURITY.md` and an `hfuzz/` fuzzing directory, and the crate is published at version 0.10.0.

### How do I use the ring crypto provider with snow?

Enable the `ring-resolver` feature to get the `resolvers::ring` module and a `RingAcceleratedResolver` you pass to `Builder::with_resolver()`, or enable `ring-accelerated` to make ring the default choice. The ring resolver does not cover BLAKE2, XChaChaPoly, P-256, 448, BLAKE2b or BLAKE3.

### Can I use snow in a no_std environment?

Yes, provided alloc is available. You set `default-features = false` and then enable each primitive you want individually, for example `use-curve25519`, `use-chacha20poly1305` and `use-blake2`. The `default-resolver` is the only built-in resolver that currently supports no_std.

### What licence is snow released under?

Dual licensed, Apache-2.0 or MIT, with both `LICENSE-APACHE` and `LICENSE-MIT` in the repository root. Contributions are dual licensed under the same terms without additional conditions, as defined in the Apache-2.0 license.

## Sources

- [Issues](https://github.com/mcginty/snow/issues)
- [License: Apache-2.0](https://github.com/mcginty/snow/blob/main/LICENSE)
- [mcginty/snow on GitHub](https://github.com/mcginty/snow)
- [README](https://github.com/mcginty/snow/blob/main/README.md)
- [Releases](https://github.com/mcginty/snow/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mcginty-snow
