diff --git a/Cargo.toml b/Cargo.toml index 382e8bc..3522e69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,15 @@ wasm-bindgen = { version = "0.2.87", optional = true } [dependencies.rand] version = "0.8.5" +default-features = false features = ["getrandom"] +optional = true [dev-dependencies] +rand = "0.8.5" pqc_core = {version = "0.3.0", features = ["load"]} + [target.'cfg(bench)'.dev-dependencies.criterion] criterion = "0.4.0" @@ -46,8 +50,10 @@ aes = [] # message that is being signed. random_signing = [] -# For compiling to wasm targets -wasm = ["wasm-bindgen", "getrandom/js"] +# For compiling to wasm targets +wasm = ["wasm-bindgen", "getrandom/js", "rand"] + +std = [] [lib] crate-type = ["cdylib", "rlib"] \ No newline at end of file diff --git a/README.md b/README.md index fdb0246..80e8507 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A rust implementation of the Dilithium, a KEM standardised by the NIST Post-Quan See the [**features**](#features) section for different options regarding security levels and modes of operation. The default security setting is Dilithium3. -It is recommended to use Dilithium in a hybrid system alongside a traditional signature algorithm such as ed25519. +It is recommended to use Dilithium in a hybrid system alongside a traditional signature algorithm such as ed25519. **Minimum Supported Rust Version: 1.50.0** @@ -23,25 +23,27 @@ It is recommended to use Dilithium in a hybrid system alongside a traditional si ```shell cargo add pqc_dilithium -``` +``` -## Usage +## Usage ```rust use pqc_dilithium::*; +use rand_core::OsRng; ``` ### Key Generation ```rust -let keys = Keypair::generate(); +use rand_core:: +let keys = Keypair::generate(&mut OsRng); assert!(keys.public.len() == PUBLICKEYBYTES); assert!(keys.expose_secret().len() == SECRETKEYBYTES); ``` -### Signing +### Signing ```rust let msg = "Hello".as_bytes(); -let sig = keys.sign(&msg); +let sig = keys.sign(&msg, &mut OsRng); assert!(sig.len() == SIGNBYTES); ``` @@ -55,7 +57,7 @@ assert!(sig_verify.is_ok()); ## AES mode -Dilithium-AES, that uses AES-256 in counter mode instead of SHAKE to +Dilithium-AES, that uses AES-256 in counter mode instead of SHAKE to expand the matrix and the masking vectors, and to sample the secret polynomials. This offers hardware speedups on certain platforms. @@ -87,7 +89,7 @@ By default this library uses Dilithium3 --- -## Testing +## Testing To run the known answer tests, you'll need to enable the `dilithium_kat` in `RUSTFLAGS` eg. @@ -120,16 +122,16 @@ For example, using [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/): wasm-pack build -- --features wasm ``` -Which will export the wasm, javascript and typescript files into `./pkg/`. +Which will export the wasm, javascript and typescript files into `./pkg/`. -To compile a different variant into a separate folder: +To compile a different variant into a separate folder: ```shell -wasm-pack build --out-dir pkg_mode5/ -- --features "wasm mode5" +wasm-pack build --out-dir pkg_mode5/ -- --features "wasm mode5" ``` There is also a basic html demo in the [www](./www/readme.md) folder. - -From the www folder run: + +From the www folder run: ```shell npm install @@ -140,11 +142,11 @@ npm run start ## Alternatives -The PQClean project has rust bindings for their C post quantum libraries. +The PQClean project has rust bindings for their C post quantum libraries. https://github.com/rustpq/pqcrypto/tree/main/pqcrypto-dilithium ---- +--- ## About @@ -152,7 +154,7 @@ Dilithium is a digital signature scheme that is strongly secure under chosen mes The official website: https://pq-crystals.org/dilithium/ -Authors of the Dilithium Algorithm: +Authors of the Dilithium Algorithm: * Roberto Avanzi, ARM Limited (DE) * Joppe Bos, NXP Semiconductors (BE) diff --git a/benches/api.rs b/benches/api.rs index b7ccfbb..599857d 100644 --- a/benches/api.rs +++ b/benches/api.rs @@ -1,20 +1,21 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use pqc_dilithium::*; +use rand_core::OsRng; fn sign_small_msg(c: &mut Criterion) { - let keys = Keypair::generate(); + let keys = Keypair::generate(&mut OsRng).unwrap(); let msg = "Hello".as_bytes(); c.bench_function("Sign Small Message", |b| { - b.iter(|| keys.sign(black_box(msg))) + b.iter(|| keys.sign(&black_box(msg), &mut OsRng).unwrap()) }); } fn verify_small_msg(c: &mut Criterion) { - let keys = Keypair::generate(); + let keys = Keypair::generate(&mut OsRng).unwrap(); let msg = "Hello".as_bytes(); - let sig = keys.sign(msg); + let sig = keys.sign(msg, &mut OsRng).unwrap(); c.bench_function("Verify Small Message", |b| { - b.iter(|| verify(black_box(sig), black_box(msg), black_box(&keys.public))) + b.iter(|| verify(black_box(&sig), black_box(msg), black_box(&keys.public))) }); } diff --git a/src/api.rs b/src/api.rs index 3a29494..92bf99f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,5 +1,7 @@ +use crate::error::*; use crate::params::{PUBLICKEYBYTES, SECRETKEYBYTES, SIGNBYTES}; use crate::sign::*; +use rand_core::{CryptoRng, RngCore}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct Keypair { @@ -14,16 +16,12 @@ impl std::fmt::Debug for Keypair { } } -pub enum SignError { - Input, - Verify, -} - impl Keypair { /// Explicitly expose secret key /// ``` /// # use pqc_dilithium::*; - /// let keys = Keypair::generate(); + /// use rand_core::OsRng; + /// let keys = Keypair::generate(&mut OsRng).expect("couldn't obtain random bytes"); /// let secret_key = keys.expose_secret(); /// assert!(secret_key.len() == SECRETKEYBYTES); /// ``` @@ -36,15 +34,19 @@ impl Keypair { /// Example: /// ``` /// # use pqc_dilithium::*; - /// let keys = Keypair::generate(); + /// # use rand_core::OsRng; + /// let keys = Keypair::generate(&mut OsRng).expect("couldn't obtain random bytes"); /// assert!(keys.public.len() == PUBLICKEYBYTES); /// assert!(keys.expose_secret().len() == SECRETKEYBYTES); /// ``` - pub fn generate() -> Keypair { + pub fn generate(rng: &mut R) -> Result + where + R: RngCore + CryptoRng, + { let mut public = [0u8; PUBLICKEYBYTES]; let mut secret = [0u8; SECRETKEYBYTES]; - crypto_sign_keypair(&mut public, &mut secret, None); - Keypair { public, secret } + crypto_sign_keypair(&mut public, &mut secret, rng, None)?; + Ok(Keypair { public, secret }) } /// Generates a signature for the given message using a keypair @@ -52,15 +54,23 @@ impl Keypair { /// Example: /// ``` /// # use pqc_dilithium::*; - /// # let keys = Keypair::generate(); + /// # use rand_core::OsRng; + /// # let keys = Keypair::generate(&mut OsRng).unwrap(); /// let msg = "Hello".as_bytes(); - /// let sig = keys.sign(&msg); + /// let sig = keys.sign(&msg, &mut OsRng).expect("couldn't obtain random bytes"); /// assert!(sig.len() == SIGNBYTES); - /// ``` - pub fn sign(&self, msg: &[u8]) -> [u8; SIGNBYTES] { + /// ``` + pub fn sign( + &self, + msg: &[u8], + rng: &mut R, + ) -> Result<[u8; SIGNBYTES], DilithiumError> + where + R: RngCore + CryptoRng, + { let mut sig = [0u8; SIGNBYTES]; - crypto_sign_signature(&mut sig, msg, &self.secret); - sig + crypto_sign_signature(&mut sig, msg, &self.secret, rng)?; + Ok(sig) } } @@ -69,18 +79,19 @@ impl Keypair { /// Example: /// ``` /// # use pqc_dilithium::*; -/// # let keys = Keypair::generate(); +/// # use rand_core::OsRng; +/// # let keys = Keypair::generate(&mut OsRng).unwrap(); /// # let msg = [0u8; 32]; -/// # let sig = keys.sign(&msg); +/// # let sig = keys.sign(&msg, &mut OsRng).unwrap(); /// let sig_verify = verify(&sig, &msg, &keys.public); /// assert!(sig_verify.is_ok()); pub fn verify( sig: &[u8], msg: &[u8], public_key: &[u8], -) -> Result<(), SignError> { +) -> Result<(), DilithiumError> { if sig.len() != SIGNBYTES { - return Err(SignError::Input); + return Err(DilithiumError::Input); } crypto_sign_verify(&sig, &msg, public_key) } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b334172 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,9 @@ +#[derive(Debug, PartialEq)] +pub enum DilithiumError { + Input, + Verify, + RandomBytesGeneration, +} + +#[cfg(feature = "std")] +impl std::error::Error for DilithiumError {} diff --git a/src/lib.rs b/src/lib.rs index 33a1226..4ea0e69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,15 @@ #[cfg(feature = "aes")] mod aes256ctr; mod api; +mod error; mod fips202; mod ntt; mod packing; mod params; mod poly; mod polyvec; -mod randombytes; mod reduce; +mod rng; mod rounding; mod sign; mod symmetric; diff --git a/src/packing.rs b/src/packing.rs index 561ffbf..77d2c4e 100644 --- a/src/packing.rs +++ b/src/packing.rs @@ -1,4 +1,4 @@ -use crate::{params::*, poly::*, polyvec::*, SignError}; +use crate::{error::DilithiumError, params::*, poly::*, polyvec::*}; /// Bit-pack public key pk = (rho, t1). pub fn pack_pk(pk: &mut [u8], rho: &[u8], t1: &Polyveck) { @@ -123,7 +123,7 @@ pub fn unpack_sig( z: &mut Polyvecl, h: &mut Polyveck, sig: &[u8], -) -> Result<(), SignError> { +) -> Result<(), DilithiumError> { let mut idx = 0usize; c[..SEEDBYTES].copy_from_slice(&sig[..SEEDBYTES]); @@ -138,12 +138,12 @@ pub fn unpack_sig( let mut k = 0usize; for i in 0..K { if sig[idx + OMEGA + i] < k as u8 || sig[idx + OMEGA + i] > OMEGA_U8 { - return Err(SignError::Input); + return Err(DilithiumError::Input); } for j in k..sig[idx + OMEGA + i] as usize { // Coefficients are ordered for strong unforgeability if j > k && sig[idx + j as usize] <= sig[idx + j as usize - 1] { - return Err(SignError::Input); + return Err(DilithiumError::Input); } h.vec[i].coeffs[sig[idx + j] as usize] = 1; } @@ -153,7 +153,7 @@ pub fn unpack_sig( // Extra indices are zero for strong unforgeability for j in k..OMEGA { if sig[idx + j as usize] > 0 { - return Err(SignError::Input); + return Err(DilithiumError::Input); } } diff --git a/src/randombytes.rs b/src/randombytes.rs deleted file mode 100644 index b67a2fc..0000000 --- a/src/randombytes.rs +++ /dev/null @@ -1,5 +0,0 @@ -use rand::prelude::*; - -pub fn randombytes(x: &mut [u8], len: usize) { - thread_rng().fill_bytes(&mut x[..len]) -} diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..249fee6 --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,16 @@ +use crate::error::DilithiumError; +use rand_core::*; + +pub fn randombytes( + x: &mut [u8], + len: usize, + rng: &mut R, +) -> Result<(), DilithiumError> +where + R: RngCore + CryptoRng, +{ + match rng.try_fill_bytes(&mut x[..len]) { + Ok(_) => Ok(()), + Err(_) => Err(DilithiumError::RandomBytesGeneration), + } +} diff --git a/src/sign.rs b/src/sign.rs index 6067211..0a54ace 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,17 +1,22 @@ use crate::{ - fips202::*, packing::*, params::*, poly::*, polyvec::*, randombytes::*, - SignError, + error::DilithiumError, fips202::*, packing::*, params::*, poly::*, + polyvec::*, rng::*, }; +use rand_core::{CryptoRng, RngCore}; -pub fn crypto_sign_keypair( +pub fn crypto_sign_keypair( pk: &mut [u8], sk: &mut [u8], + rng: &mut R, seed: Option<&[u8]>, -) -> u8 { +) -> Result<(), DilithiumError> +where + R: RngCore + CryptoRng, +{ let mut init_seed = [0u8; SEEDBYTES]; match seed { Some(x) => init_seed.copy_from_slice(x), - None => randombytes(&mut init_seed, SEEDBYTES), + None => randombytes(&mut init_seed, SEEDBYTES, rng)?, }; let mut seedbuf = [0u8; 2 * SEEDBYTES + CRHBYTES]; let mut tr = [0u8; SEEDBYTES]; @@ -61,10 +66,18 @@ pub fn crypto_sign_keypair( shake256(&mut tr, SEEDBYTES, pk, PUBLICKEYBYTES); pack_sk(sk, &rho, &tr, &key, &t0, &s1, &s2); - return 0; + Ok(()) } -pub fn crypto_sign_signature(sig: &mut [u8], m: &[u8], sk: &[u8]) { +pub fn crypto_sign_signature( + sig: &mut [u8], + m: &[u8], + sk: &[u8], + rng: &mut R, +) -> Result<(), DilithiumError> +where + R: RngCore + CryptoRng, +{ // `key` and `mu` are concatenated let mut keymu = [0u8; SEEDBYTES + CRHBYTES]; @@ -97,7 +110,7 @@ pub fn crypto_sign_signature(sig: &mut [u8], m: &[u8], sk: &[u8]) { shake256_squeeze(&mut keymu[SEEDBYTES..], CRHBYTES, &mut state); if RANDOMIZED_SIGNING { - randombytes(&mut rhoprime, CRHBYTES); + randombytes(&mut rhoprime, CRHBYTES, rng)?; } else { shake256(&mut rhoprime, CRHBYTES, &keymu, SEEDBYTES + CRHBYTES); } @@ -168,7 +181,7 @@ pub fn crypto_sign_signature(sig: &mut [u8], m: &[u8], sk: &[u8]) { // Write signature pack_sig(sig, None, &z, &h); - return; + return Ok(()); } } @@ -176,7 +189,7 @@ pub fn crypto_sign_verify( sig: &[u8], m: &[u8], pk: &[u8], -) -> Result<(), SignError> { +) -> Result<(), DilithiumError> { let mut buf = [0u8; K * POLYW1_PACKEDBYTES]; let mut rho = [0u8; SEEDBYTES]; let mut mu = [0u8; CRHBYTES]; @@ -192,7 +205,7 @@ pub fn crypto_sign_verify( let mut state = KeccakState::default(); // shake256_init() if sig.len() != SIGNBYTES { - return Err(SignError::Input); + return Err(DilithiumError::Input); } unpack_pk(&mut rho, &mut t1, pk); @@ -200,7 +213,7 @@ pub fn crypto_sign_verify( return Err(e); } if polyvecl_chknorm(&z, (GAMMA1 - BETA) as i32) > 0 { - return Err(SignError::Input); + return Err(DilithiumError::Input); } // Compute CRH(CRH(rho, t1), msg) @@ -240,7 +253,7 @@ pub fn crypto_sign_verify( shake256_squeeze(&mut c2, SEEDBYTES, &mut state); // Doesn't require constant time equality check if c != c2 { - Err(SignError::Verify) + Err(DilithiumError::Verify) } else { Ok(()) } diff --git a/src/wasm.rs b/src/wasm.rs index 123e49b..3cc5c04 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -12,16 +12,21 @@ pub struct Keys { } #[wasm_bindgen] -pub fn keypair() -> Keys { - Keys { - keypair: api::Keypair::generate(), +pub fn keypair() -> Result { + let mut rng = rand::rngs::OsRng {}; + match api::Keypair::generate(&mut rng) { + Ok(keypair) => Ok(Keys { keypair }), + Err(error::DilithiumError::RandomBytesGeneration) => { + Err(JsError::new("Error trying to fill random bytes")) + } + _ => Err(JsError::new("The keypair could not be generated")), } } #[wasm_bindgen] impl Keys { #[wasm_bindgen(constructor)] - pub fn new() -> Keys { + pub fn new() -> Result { keypair() } @@ -36,8 +41,12 @@ impl Keys { } #[wasm_bindgen] - pub fn sign(&self, msg: Box<[u8]>) -> Box<[u8]> { - Box::new(self.keypair.sign(&msg)) + pub fn sign(&self, msg: Box<[u8]>) -> Result, JsValue> { + let mut rng = rand::rngs::OsRng {}; + match self.keypair.sign(&msg, &mut rng) { + Ok(signature) => Ok(Box::new(signature)), + Err(_) => Err(JsValue::null()), + } } } diff --git a/tests/integration.rs b/tests/integration.rs index cd0dfd9..d9958be 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,17 +2,19 @@ use pqc_dilithium::*; #[test] fn sign_then_verify_valid() { + let mut rng = rand::thread_rng(); let msg = b"Hello"; - let keys = Keypair::generate(); - let signature = keys.sign(msg); + let keys = Keypair::generate(&mut rng).unwrap(); + let signature = keys.sign(msg, &mut rng).unwrap(); assert!(verify(&signature, msg, &keys.public).is_ok()) } #[test] fn sign_then_verify_invalid() { + let mut rng = rand::thread_rng(); let msg = b"Hello"; - let keys = Keypair::generate(); - let mut signature = keys.sign(msg); + let keys = Keypair::generate(&mut rng).unwrap(); + let mut signature = keys.sign(msg, &mut rng).unwrap(); signature[..4].copy_from_slice(&[255u8; 4]); assert!(verify(&signature, msg, &keys.public).is_err()) } diff --git a/tests/kat.rs b/tests/kat.rs index 5300946..897193c 100644 --- a/tests/kat.rs +++ b/tests/kat.rs @@ -2,6 +2,7 @@ use pqc_core::load::*; use pqc_dilithium::*; +use rand_core::OsRng; use std::path::PathBuf; const MODE: u8 = if cfg!(feature = "mode2") { @@ -25,7 +26,8 @@ fn keypair() { let sk = kat.sk.clone(); let mut pk2 = [0u8; PUBLICKEYBYTES]; let mut sk2 = [0u8; SECRETKEYBYTES]; - crypto_sign_keypair(&mut pk2, &mut sk2, Some(&bufvec[i])); + crypto_sign_keypair(&mut pk2, &mut sk2, &mut OsRng, Some(&bufvec[i])) + .unwrap(); assert_eq!(pk, pk2); assert_eq!(sk, sk2); } @@ -41,7 +43,7 @@ pub fn sign() { let msg = kat.msg.clone(); let sk = kat.sk.clone(); let mut sig = vec![0u8; SIGNBYTES]; - crypto_sign_signature(&mut sig, &msg, &sk); + crypto_sign_signature(&mut sig, &msg, &sk, &mut OsRng).unwrap(); assert_eq!(sm[..SIGNBYTES], sig); } } diff --git a/tests/wasm.js b/tests/wasm.js index 34ba6c1..a1e4544 100644 --- a/tests/wasm.js +++ b/tests/wasm.js @@ -2,7 +2,7 @@ // Uses CommonJS modules // Package needs to be built for node: -// wasm-pack build --target nodejs -- features wasm +// wasm-pack build --target nodejs -- --features wasm const dilithium = require("../pkg/pqc_dilithium"); const assert = require("assert");