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