A pure-Rust implementation of Streamlined NTRU Prime for all parameter sizes.
NTRU Prime is a lattice-based cryptosystem aiming to improve the security of lattice schemes at minimal cost. It is thought to be resistant to quantum computing advances, in particular Shor's algorithm. It made it to NIST final round but was not selected for finalization.
Please read the warnings before use.
The algorithm was authored by Daniel J. Bernstein, Chitchanok Chuengsatiansup, Tanja Lange & Christine van Vredendaal. This implementation is aligned with the PQClean reference and verified against the IETF draft KAT vectors.
| Parameter Set | NIST Level | P | Q | W | Public Key | Secret Key | Ciphertext | Shared Secret |
|---|---|---|---|---|---|---|---|---|
| sntrup653 | 1 | 653 | 4621 | 288 | 994 | 1518 | 897 | 32 |
| sntrup761 | 2 | 761 | 4591 | 286 | 1158 | 1763 | 1039 | 32 |
| sntrup857 | 3 | 857 | 5167 | 322 | 1322 | 1999 | 1184 | 32 |
| sntrup953 | 4 | 953 | 6343 | 396 | 1505 | 2254 | 1349 | 32 |
| sntrup1013 | 5 | 1013 | 7177 | 448 | 1623 | 2417 | 1455 | 32 |
| sntrup1277 | 5 | 1277 | 7879 | 492 | 2067 | 3059 | 1847 | 32 |
All key and ciphertext sizes are in bytes. Sizes are fixed per parameter set using a canonical encoding enforced by the code.
Note: sntrup653 (NIST Level 1) is recommended for research and testing only. Prefer sntrup761 or higher for production use.
- Pure Rust,
no_std-compatible, dependency-minimal - All six parameter sizes: sntrup653, sntrup761, sntrup857, sntrup953, sntrup1013, sntrup1277
- IND-CCA2 secure with implicit rejection
- Constant-time operations throughout (branchless sort, constant-time comparison and selection)
- SIMD acceleration (AVX2 on x86_64, NEON on aarch64) with automatic detection
- Optional
serdesupport via theserdefeature - Deterministic key generation from a 32-byte seed
The KEM API is split into three default features so downstream crates can pull in only what they need:
| Feature | Default | Description |
|---|---|---|
kgen |
yes | Key generation: SntrupKem::generate_key, SntrupKem::generate_key_deterministic |
ecap |
yes | Encapsulation: EncapsulationKey::encapsulate |
dcap |
yes | Decapsulation: DecapsulationKey::decapsulate |
kem |
no | Implements the kem crate traits for every parameter set |
force-scalar |
no | Disable SIMD (AVX2/NEON) and use pure-Rust scalar code |
serde |
no | Enables Serialize/Deserialize for all key and ciphertext types (via serdect for constant-time hex encoding) |
js |
no | Enables WebAssembly support for wasm32-unknown-unknown by configuring getrandom to use JavaScript's crypto.getRandomValues() |
To use only a subset of the KEM API, disable defaults and pick the features you need:
[dependencies]
# Decapsulation only (e.g. a receiver that never generates keys or encapsulates)
sntrup-kem = { version = "0.1", default-features = false, features = ["dcap"] }use sntrup_kem::{Sntrup761, SntrupKem};
let mut rng = rand::rng();
let (encapsulation_key, decapsulation_key) = Sntrup761::generate_key(&mut rng);All six parameter sets are available as type aliases:
use sntrup_kem::{Sntrup653, Sntrup761, Sntrup857, Sntrup953, Sntrup1013, Sntrup1277, SntrupKem};
let mut rng = rand::rng();
let (ek_653, dk_653) = Sntrup653::generate_key(&mut rng);
let (ek_761, dk_761) = Sntrup761::generate_key(&mut rng);
let (ek_857, dk_857) = Sntrup857::generate_key(&mut rng);
let (ek_953, dk_953) = Sntrup953::generate_key(&mut rng);
let (ek_1013, dk_1013) = Sntrup1013::generate_key(&mut rng);
let (ek_1277, dk_1277) = Sntrup1277::generate_key(&mut rng);Or use the convenience modules with parameter-specific types:
let mut rng = rand::rng();
let (ek, dk) = sntrup_kem::sntrup761::generate_key(&mut rng);The sender uses the encapsulation (public) key to produce a ciphertext and shared secret:
use sntrup_kem::{Sntrup761, SntrupKem};
let mut rng = rand::rng();
let (encapsulation_key, decapsulation_key) = Sntrup761::generate_key(&mut rng);
// Sender side
let (ciphertext, shared_secret_sender) = encapsulation_key.encapsulate(&mut rng);The receiver uses the decapsulation (secret) key and the ciphertext to recover the shared secret:
use sntrup_kem::{Sntrup761, SntrupKem};
let mut rng = rand::rng();
let (encapsulation_key, decapsulation_key) = Sntrup761::generate_key(&mut rng);
let (ciphertext, shared_secret_sender) = encapsulation_key.encapsulate(&mut rng);
// Receiver side — implicit rejection: always returns a key
let shared_secret_receiver = decapsulation_key.decapsulate(&ciphertext);
assert_eq!(shared_secret_sender, shared_secret_receiver);Derive the same keypair from a 32-byte seed:
use sntrup_kem::{Sntrup761, SntrupKem};
let seed = [0x42u8; 32]; // must come from a cryptographically secure source
let (ek1, dk1) = Sntrup761::generate_key_deterministic(&seed);
let (ek2, dk2) = Sntrup761::generate_key_deterministic(&seed);
assert_eq!(ek1, ek2);
assert_eq!(dk1, dk2);Enable the serde feature:
sntrup-kem = { version = "0.1", features = ["serde"] }Keys and ciphertexts serialize to hex in human-readable formats (JSON) and raw bytes in binary formats (postcard, bincode):
use sntrup_kem::{Sntrup761, SntrupKem, EncapsulationKey, Sntrup761Params};
let mut rng = rand::rng();
let (ek, dk) = Sntrup761::generate_key(&mut rng);
let json = serde_json::to_string(&ek).unwrap();
let ek2: EncapsulationKey<Sntrup761Params> = serde_json::from_str(&json).unwrap();
assert_eq!(ek, ek2);All types support AsRef<[u8]> and TryFrom<&[u8]>:
use sntrup_kem::{Sntrup761, SntrupKem, EncapsulationKey, Sntrup761Params};
let mut rng = rand::rng();
let (ek, dk) = Sntrup761::generate_key(&mut rng);
// Serialize to bytes
let ek_bytes: &[u8] = ek.as_ref();
// Deserialize from bytes (validates size)
let ek2 = EncapsulationKey::<Sntrup761Params>::try_from(ek_bytes).unwrap();
assert_eq!(ek, ek2);Enable the kem feature to use any parameter set through the generic
kem traits:
# #[cfg(feature = "kem")] {
use rand::SeedableRng;
use rand::rngs::{StdRng, SysRng};
use sntrup_kem::kem::{Decapsulate, Encapsulate, Kem, Sntrup761Params};
let Ok(mut rng) = StdRng::try_from_rng(&mut SysRng) else {
return;
};
let (dk, ek) = Sntrup761Params::generate_keypair_from_rng(&mut rng);
let (ct, sent) = ek.encapsulate_with_rng(&mut rng);
assert_eq!(dk.decapsulate(&ct), sent);
# }The module re-exports the traits and parameter-set marker types. See
examples/kem_traits.rs for generic use and key import/export.
To compile for wasm32-unknown-unknown, enable the js feature so that getrandom uses JavaScript's crypto.getRandomValues() for randomness:
[dependencies]
sntrup-kem = { version = "0.1", features = ["js"] }Install the target and build:
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --features jsFor wasm32-wasi (or wasm32-wasip1), the js feature is not needed since WASI provides its own random source.
- IND-CCA2 security via implicit rejection: decapsulation always returns a shared key. On failure, a pseudorandom key is derived from secret randomness (
rho), making it indistinguishable from a valid key to an attacker. - Hash domain separation: all hashes use prefix bytes (following the NTRU Prime specification).
- Constant-time operations: branchless sorting (djbsort), constant-time weight checks, constant-time ciphertext comparison, and constant-time selection in decapsulation.
- Zeroization: secret key material is zeroized on drop.
This implementation has not undergone any security auditing and while care has been taken no guarantees can be made for either correctness or the constant time running of the underlying functions. Please use at your own risk.
Streamlined NTRU Prime was first published in 2016. The algorithm still requires careful security review. Please see here for further warnings from the authors regarding NTRU Prime and lattice-based encryption schemes.
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.