Skip to content

Instantly share code, notes, and snippets.

@eaon
Last active October 23, 2023 02:54
Show Gist options
  • Save eaon/fff807d92afad3baf119742a189eab2d to your computer and use it in GitHub Desktop.
Save eaon/fff807d92afad3baf119742a189eab2d to your computer and use it in GitHub Desktop.
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<R>(rng: &mut R) -> Result<Keypair, DilithiumError>
+ 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<R>(
+ &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<R>(
+ 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<R>(
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<R>(
+ 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<Keys, JsError> {
+ 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<Keys, JsError> {
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<Box<[u8]>, 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");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment