Skip to main content

sntrup_sys/
lib.rs

1//! Rust FFI bindings over the extracted, deduplicated SUPERCOP Streamlined
2//! NTRU Prime sources in `vendor/`. Every module wraps one parameter set,
3//! all sharing one compiled copy of `vendor/common/` (see
4//! `vendor/NOTICE.md` for why the split between shared and per-parameter-
5//! set code is drawn where it is). Each enabled parameter set gets its own
6//! module (gated by the matching Cargo feature), exposing `keypair()`,
7//! `encapsulate(pk)`, and `decapsulate(c, sk)`.
8//!
9//! # Randomness
10//!
11//! The vendored C code calls a single external `randombytes` C function for
12//! all key generation and encapsulation randomness. `vendor/common/namespace.h`
13//! renames it to `sntrup_sys_randombytes`, which this crate implements here
14//! using `getrandom` (the OS CSPRNG), satisfying the one external symbol every
15//! vendored directory expects (see vendor/NOTICE.md). The prefix keeps it from
16//! clashing with other C libraries that export a plain `randombytes`, such as
17//! libsodium.
18//!
19//! # Zeroization
20//!
21//! The secret key (from `keypair()`) and the shared secret (from
22//! `encapsulate()`/`decapsulate()`) are returned as
23//! `Zeroizing<[u8; N]>` (from the `zeroize` crate): a fixed-size,
24//! stack-allocated buffer that's wiped on drop. The FFI call writes
25//! directly into that buffer -- there's no intermediate `Vec` the secret
26//! passes through first, so there's nothing left unzeroized after the
27//! `Zeroizing` wrapper does its job. `decapsulate()` also *takes* `sk` as
28//! `&Zeroizing<[u8; SECRET_KEY_BYTES]>` rather than `&[u8]`, so a secret
29//! key that was never wrapped in `Zeroizing` in the first place can't be
30//! passed in by accident -- the type is part of the contract, not just a
31//! runtime length check. The public key and ciphertext are not secret and
32//! stay plain `Vec<u8>`/`&[u8]`.
33
34/// # Safety
35///
36/// `buf` must be valid for writes of `buf_len` bytes and not aliased by any
37/// other live reference for the duration of this call. The vendored C code
38/// upholds this by construction (it always passes a real buffer of exactly
39/// `buf_len` bytes) but Rust can't verify that across the FFI boundary, so
40/// the contract is on the caller, hence `unsafe fn`.
41#[unsafe(no_mangle)]
42pub unsafe extern "C" fn sntrup_sys_randombytes(buf: *mut u8, buf_len: u64) {
43    let slice = unsafe { std::slice::from_raw_parts_mut(buf, buf_len as usize) };
44    getrandom::fill(slice).expect("OS randomness source failed");
45}
46
47#[cfg(feature = "sntrup653")]
48pub mod sntrup653 {
49    use std::os::raw::c_int;
50    use zeroize::Zeroizing;
51
52    pub const PUBLIC_KEY_BYTES: usize = 994;
53    pub const SECRET_KEY_BYTES: usize = 1518;
54    pub const CIPHERTEXT_BYTES: usize = 897;
55    pub const SHARED_SECRET_BYTES: usize = 32;
56
57    unsafe extern "C" {
58        fn sntrup653_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
59        fn sntrup653_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
60        fn sntrup653_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
61    }
62
63    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
64    /// secret key is zeroized on drop.
65    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
66        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
67        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
68        let rc = unsafe { sntrup653_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
69        assert_eq!(rc, 0, "sntrup653_ref_crypto_kem_keypair failed");
70        (pk, sk)
71    }
72
73    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
74    /// shared secret is zeroized on drop.
75    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
76        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
77        let mut c = vec![0u8; CIPHERTEXT_BYTES];
78        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
79        let rc =
80            unsafe { sntrup653_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
81        assert_eq!(rc, 0, "sntrup653_ref_crypto_kem_enc failed");
82        (c, ss)
83    }
84
85    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
86    ///
87    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
88    /// returns) rather than a bare `&[u8]`, so the type system rules out
89    /// passing a secret key that was never protected by `Zeroizing` in the
90    /// first place; its length is therefore already guaranteed by the type,
91    /// with nothing left to check at runtime.
92    ///
93    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
94    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
95    /// rejection) -- it does not signal failure via the return value, by
96    /// design, to avoid a decryption-failure oracle.
97    pub fn decapsulate(
98        c: &[u8],
99        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
100    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
101        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
102        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
103        unsafe { sntrup653_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
104        ss
105    }
106}
107
108#[cfg(feature = "sntrup761")]
109pub mod sntrup761 {
110    use std::os::raw::c_int;
111    use zeroize::Zeroizing;
112
113    pub const PUBLIC_KEY_BYTES: usize = 1158;
114    pub const SECRET_KEY_BYTES: usize = 1763;
115    pub const CIPHERTEXT_BYTES: usize = 1039;
116    pub const SHARED_SECRET_BYTES: usize = 32;
117
118    unsafe extern "C" {
119        fn sntrup761_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
120        fn sntrup761_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
121        fn sntrup761_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
122    }
123
124    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
125    /// secret key is zeroized on drop.
126    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
127        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
128        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
129        let rc = unsafe { sntrup761_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
130        assert_eq!(rc, 0, "sntrup761_ref_crypto_kem_keypair failed");
131        (pk, sk)
132    }
133
134    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
135    /// shared secret is zeroized on drop.
136    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
137        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
138        let mut c = vec![0u8; CIPHERTEXT_BYTES];
139        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
140        let rc =
141            unsafe { sntrup761_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
142        assert_eq!(rc, 0, "sntrup761_ref_crypto_kem_enc failed");
143        (c, ss)
144    }
145
146    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
147    ///
148    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
149    /// returns) rather than a bare `&[u8]`, so the type system rules out
150    /// passing a secret key that was never protected by `Zeroizing` in the
151    /// first place; its length is therefore already guaranteed by the type,
152    /// with nothing left to check at runtime.
153    ///
154    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
155    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
156    /// rejection) -- it does not signal failure via the return value, by
157    /// design, to avoid a decryption-failure oracle.
158    pub fn decapsulate(
159        c: &[u8],
160        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
161    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
162        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
163        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
164        unsafe { sntrup761_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
165        ss
166    }
167}
168
169#[cfg(feature = "sntrup857")]
170pub mod sntrup857 {
171    use std::os::raw::c_int;
172    use zeroize::Zeroizing;
173
174    pub const PUBLIC_KEY_BYTES: usize = 1322;
175    pub const SECRET_KEY_BYTES: usize = 1999;
176    pub const CIPHERTEXT_BYTES: usize = 1184;
177    pub const SHARED_SECRET_BYTES: usize = 32;
178
179    unsafe extern "C" {
180        fn sntrup857_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
181        fn sntrup857_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
182        fn sntrup857_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
183    }
184
185    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
186    /// secret key is zeroized on drop.
187    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
188        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
189        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
190        let rc = unsafe { sntrup857_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
191        assert_eq!(rc, 0, "sntrup857_ref_crypto_kem_keypair failed");
192        (pk, sk)
193    }
194
195    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
196    /// shared secret is zeroized on drop.
197    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
198        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
199        let mut c = vec![0u8; CIPHERTEXT_BYTES];
200        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
201        let rc =
202            unsafe { sntrup857_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
203        assert_eq!(rc, 0, "sntrup857_ref_crypto_kem_enc failed");
204        (c, ss)
205    }
206
207    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
208    ///
209    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
210    /// returns) rather than a bare `&[u8]`, so the type system rules out
211    /// passing a secret key that was never protected by `Zeroizing` in the
212    /// first place; its length is therefore already guaranteed by the type,
213    /// with nothing left to check at runtime.
214    ///
215    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
216    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
217    /// rejection) -- it does not signal failure via the return value, by
218    /// design, to avoid a decryption-failure oracle.
219    pub fn decapsulate(
220        c: &[u8],
221        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
222    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
223        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
224        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
225        unsafe { sntrup857_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
226        ss
227    }
228}
229
230#[cfg(feature = "sntrup953")]
231pub mod sntrup953 {
232    use std::os::raw::c_int;
233    use zeroize::Zeroizing;
234
235    pub const PUBLIC_KEY_BYTES: usize = 1505;
236    pub const SECRET_KEY_BYTES: usize = 2254;
237    pub const CIPHERTEXT_BYTES: usize = 1349;
238    pub const SHARED_SECRET_BYTES: usize = 32;
239
240    unsafe extern "C" {
241        fn sntrup953_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
242        fn sntrup953_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
243        fn sntrup953_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
244    }
245
246    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
247    /// secret key is zeroized on drop.
248    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
249        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
250        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
251        let rc = unsafe { sntrup953_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
252        assert_eq!(rc, 0, "sntrup953_ref_crypto_kem_keypair failed");
253        (pk, sk)
254    }
255
256    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
257    /// shared secret is zeroized on drop.
258    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
259        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
260        let mut c = vec![0u8; CIPHERTEXT_BYTES];
261        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
262        let rc =
263            unsafe { sntrup953_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
264        assert_eq!(rc, 0, "sntrup953_ref_crypto_kem_enc failed");
265        (c, ss)
266    }
267
268    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
269    ///
270    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
271    /// returns) rather than a bare `&[u8]`, so the type system rules out
272    /// passing a secret key that was never protected by `Zeroizing` in the
273    /// first place; its length is therefore already guaranteed by the type,
274    /// with nothing left to check at runtime.
275    ///
276    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
277    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
278    /// rejection) -- it does not signal failure via the return value, by
279    /// design, to avoid a decryption-failure oracle.
280    pub fn decapsulate(
281        c: &[u8],
282        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
283    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
284        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
285        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
286        unsafe { sntrup953_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
287        ss
288    }
289}
290
291#[cfg(feature = "sntrup1013")]
292pub mod sntrup1013 {
293    use std::os::raw::c_int;
294    use zeroize::Zeroizing;
295
296    pub const PUBLIC_KEY_BYTES: usize = 1623;
297    pub const SECRET_KEY_BYTES: usize = 2417;
298    pub const CIPHERTEXT_BYTES: usize = 1455;
299    pub const SHARED_SECRET_BYTES: usize = 32;
300
301    unsafe extern "C" {
302        fn sntrup1013_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
303        fn sntrup1013_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
304        fn sntrup1013_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
305    }
306
307    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
308    /// secret key is zeroized on drop.
309    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
310        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
311        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
312        let rc = unsafe { sntrup1013_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
313        assert_eq!(rc, 0, "sntrup1013_ref_crypto_kem_keypair failed");
314        (pk, sk)
315    }
316
317    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
318    /// shared secret is zeroized on drop.
319    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
320        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
321        let mut c = vec![0u8; CIPHERTEXT_BYTES];
322        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
323        let rc =
324            unsafe { sntrup1013_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
325        assert_eq!(rc, 0, "sntrup1013_ref_crypto_kem_enc failed");
326        (c, ss)
327    }
328
329    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
330    ///
331    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
332    /// returns) rather than a bare `&[u8]`, so the type system rules out
333    /// passing a secret key that was never protected by `Zeroizing` in the
334    /// first place; its length is therefore already guaranteed by the type,
335    /// with nothing left to check at runtime.
336    ///
337    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
338    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
339    /// rejection) -- it does not signal failure via the return value, by
340    /// design, to avoid a decryption-failure oracle.
341    pub fn decapsulate(
342        c: &[u8],
343        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
344    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
345        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
346        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
347        unsafe { sntrup1013_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
348        ss
349    }
350}
351
352#[cfg(feature = "sntrup1277")]
353pub mod sntrup1277 {
354    use std::os::raw::c_int;
355    use zeroize::Zeroizing;
356
357    pub const PUBLIC_KEY_BYTES: usize = 2067;
358    pub const SECRET_KEY_BYTES: usize = 3059;
359    pub const CIPHERTEXT_BYTES: usize = 1847;
360    pub const SHARED_SECRET_BYTES: usize = 32;
361
362    unsafe extern "C" {
363        fn sntrup1277_ref_crypto_kem_keypair(pk: *mut u8, sk: *mut u8) -> c_int;
364        fn sntrup1277_ref_crypto_kem_enc(c: *mut u8, k: *mut u8, pk: *const u8) -> c_int;
365        fn sntrup1277_ref_crypto_kem_dec(k: *mut u8, c: *const u8, sk: *const u8) -> c_int;
366    }
367
368    /// Generate a fresh keypair. Returns `(public_key, secret_key)`; the
369    /// secret key is zeroized on drop.
370    pub fn keypair() -> (Vec<u8>, Zeroizing<[u8; SECRET_KEY_BYTES]>) {
371        let mut pk = vec![0u8; PUBLIC_KEY_BYTES];
372        let mut sk = Zeroizing::new([0u8; SECRET_KEY_BYTES]);
373        let rc = unsafe { sntrup1277_ref_crypto_kem_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()) };
374        assert_eq!(rc, 0, "sntrup1277_ref_crypto_kem_keypair failed");
375        (pk, sk)
376    }
377
378    /// Encapsulate against `pk`. Returns `(ciphertext, shared_secret)`; the
379    /// shared secret is zeroized on drop.
380    pub fn encapsulate(pk: &[u8]) -> (Vec<u8>, Zeroizing<[u8; SHARED_SECRET_BYTES]>) {
381        assert_eq!(pk.len(), PUBLIC_KEY_BYTES, "invalid public key length");
382        let mut c = vec![0u8; CIPHERTEXT_BYTES];
383        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
384        let rc =
385            unsafe { sntrup1277_ref_crypto_kem_enc(c.as_mut_ptr(), ss.as_mut_ptr(), pk.as_ptr()) };
386        assert_eq!(rc, 0, "sntrup1277_ref_crypto_kem_enc failed");
387        (c, ss)
388    }
389
390    /// Decapsulate `c` using `sk`. Returns the shared secret, zeroized on drop.
391    ///
392    /// `sk` must be a `Zeroizing`-wrapped secret key (exactly what `keypair()`
393    /// returns) rather than a bare `&[u8]`, so the type system rules out
394    /// passing a secret key that was never protected by `Zeroizing` in the
395    /// first place; its length is therefore already guaranteed by the type,
396    /// with nothing left to check at runtime.
397    ///
398    /// Per the Streamlined NTRU Prime KEM spec this always returns *some*
399    /// 32-byte value, even for an invalid/malformed ciphertext (implicit
400    /// rejection) -- it does not signal failure via the return value, by
401    /// design, to avoid a decryption-failure oracle.
402    pub fn decapsulate(
403        c: &[u8],
404        sk: &Zeroizing<[u8; SECRET_KEY_BYTES]>,
405    ) -> Zeroizing<[u8; SHARED_SECRET_BYTES]> {
406        assert_eq!(c.len(), CIPHERTEXT_BYTES, "invalid ciphertext length");
407        let mut ss = Zeroizing::new([0u8; SHARED_SECRET_BYTES]);
408        unsafe { sntrup1277_ref_crypto_kem_dec(ss.as_mut_ptr(), c.as_ptr(), sk.as_ptr()) };
409        ss
410    }
411}