#pqc #post-quantum #cryptography #lattice

rusty-saber

Pure rust implementation of the PQC scheme Saber

1 stable release

Uses new Rust 2021

1.0.0 Mar 5, 2022

#657 in Cryptography

MIT license

140KB
3.5K SLoC

Rust 2K SLoC // 0.0% comments C 1K SLoC // 0.1% comments

rusty-saber

A safe pure-rust implementation of the Saber post-quantum scheme.

  • Saber is a lattice-based key encapsulation mechanism (KEM)
  • The implementation is based on the Saber reference implementation of NIST round 3
  • The implementation does not utilize any concurrency techniques (SIMD/threading/…, except maybe auto-vectorization for your CPU)
  • It depends on sha3 as SHA-3 implementation and aes as AES block cipher (used as RNG) implementation
  • It passes the 100 testcases of the C reference implementation
  • The C reference implementation is included in this distribution since it is used for tests
  • It implements the three variants: LightSaber, Saber, FireSaber
  • The KEM takes about 25 milliseconds (all three variants) to run on a modern computer
  • The implementation is constant-time on software instruction level
  • The random number generator is based on AES256 in counter mode

Who should use it?

Anyone, how wants to utilize Saber to negotiate a symmetric key between two parties.

How does one use it?

Add this to your Cargo.toml:

[dependencies]
rusty-saber = "1.0"

To use a specific Saber variant, you need to import it with the corresponding feature flag:

[dependencies]
rusty-saber = { version = "1.0", features = ["lightsaber"] }

Feature flags for the three variants are called lightsaber, saber, and firesaber respectively.

The simple example illustrates the API:

use rusty_saber::api::{
  CRYPTO_BYTES, CRYPTO_CIPHERTEXTBYTES, CRYPTO_PUBLICKEYBYTES, CRYPTO_SECRETKEYBYTES,
};
use rusty_saber::kem::{crypto_kem_dec, crypto_kem_enc, crypto_kem_keypair};
use rusty_saber::rng::AesState;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
  let mut pk = [0u8; CRYPTO_PUBLICKEYBYTES];
  let mut sk = [0u8; CRYPTO_SECRETKEYBYTES];
  let mut ct = [0u8; CRYPTO_CIPHERTEXTBYTES];
  let mut ss_a = [0u8; CRYPTO_BYTES];
  let mut ss_b = [0u8; CRYPTO_BYTES];
  let mut rng = AesState::new();
  //rng.randombytes_init([0u8; 48]);  // TODO use a proper seed (like bytes from /dev/urandom) here

  // Party a: generate public key `pk` and secret key `sk`
  crypto_kem_keypair(&mut pk, &mut sk, &mut rng)?;
  // Party b: generate a shared secret `ss_a` and ciphertext `ct` from the public key `pk`
  crypto_kem_enc(&mut ct, &mut ss_a, &mut pk, &mut rng)?;
  // Party a: derive the same shared secret `ss_b` from the ciphertext `ct` and the secret key `sk`
  crypto_kem_dec(&mut ss_b, &mut ct, &mut sk)?;

  assert_eq!(ss_a, ss_b);
  Ok(())
}

How does one run it?

This library comes with two examples:

$ cargo run --example simple

The pqcgenkat_kem example implements the classic request/response file structure which is part of the NIST PQC framework.

$ cargo run --example pqcgenkat_kem
$ ls *.r??
PQCkemKAT_2304.req  PQCkemKAT_2304.rsp
$ tail -n 2 PQCkemKAT_2304.rsp
ss = E5256B4F25816367FBE235E47C25ABB78195CEF7DE3F9C77926839F209CDF652

The different variants can be enabled through feature flags:

$ cargo run --example pqcgenkat_kem --features lightsaber
$ ls *.r??
PQCkemKAT_1568.req  PQCkemKAT_1568.rsp

saber is the default variant. Unfortunately, you cannot enable two variants simultaneously.

Is it correct?

Yes. You can run unittests with the following commands:

$ cargo test --features cref,lightsaber
$ cargo test --features cref
$ cargo test --features cref,firesaber

It compares the output of function calls with its C equivalent. Besides unittests, you can generate the pqcgenkat_kem req/rsp files and compare them to the ones generated by the C reference implementation. We verified that they are equivalent.

Is it fast?

Yes, but it takes roughly 16.2% more runtime than the C implementation. Here, data is always mentioned with clock cycles as unit. The rust implementation has the following clock-cycle count characteristics (the smaller the better):

complete KEMkeypairencdec
lightsaber329,96486,665116,139121,433
saber586,544182,183216,528232,882
firesaber923,330282,467318,043335,297

The C reference implementation has the following clock-cycle count characteristics (the smaller the better):

complete KEMkeypairencdec
lightsaber284,55872,78595,936115,837
saber509,361140,370174,995193,996
firesaber785,548222,955268,561294,032

The tests were done on a Lenovo Thinkpad x260 (Intel Core i5-6200U CPU @ 2.30GHz). In the case of rust, criterion 0.3.5 has been used as given in benches/ and in case of C, the rudimentary code utilizing the TSC register provided with the reference implementation is used. I disabled CPU frequency scaling before running experiments. You can run the benchmark suite yourself with the bench subcommand, the cref feature and optionally some variant feature flag:

$ cargo bench --features cref,lightsaber
$ cargo bench --features cref
$ cargo bench --features cref,firesaber

Where is the source code?

On github.

What is the content's license?

MIT License

Changelog

  • Version 1.0.0: public release

Where can I ask you to fix a bug?

On github.

Dependencies

~1.5MB
~18K SLoC