diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f2737..ee3ed90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.1 (2024-12-22) + +- Added keygen with seeds for deterministic keygen +- Added two fuzzing harnesses (still some work to go) + +## 0.4.0 (2024-10-04) + +- Updated to FIPS 205 final spec + ## 0.1.2 (2024-03-15) - Internal improvements, removed dependency on generic-array, MSRV at 1.70 diff --git a/Cargo.toml b/Cargo.toml index c163f3e..da4e6d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ -workspace = { members = ['ffi'], exclude = ["dudect", "ct_cm4"] } +workspace = { members = ['ffi'], exclude = ["dudect", "ct_cm4", "fuzz"] } [package] name = "fips205" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT OR Apache-2.0" description = "FIPS 205: Stateless Hash-Based Digital Signature Standard" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..035369a --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "fips205-fuzz" +version = "0.4.1" +authors = ["Eric Schorn "] +description = "Fuzz harness for FIPS 205 (release) ML-DSA" +edition = "2021" +license = "MIT OR Apache-2.0" +publish = false +rust-version = "1.70" + + +[package.metadata] +cargo-fuzz = true + + +[dependencies] +libfuzzer-sys = "0.4" +rand_core = { version = "0.6.4", default-features = false } +rand_chacha = "0.3.1" +arbitrary = { version = "1.0", features = ["derive"] } + +[dependencies.fips205] +path = ".." + + +# Prevent this from interfering with workspaces +#[workspace] +#members = ["."] + + +[profile.release] +opt-level = 0 +debug = true +debug-assertions = true +overflow-checks = true + + +[[bin]] +name = "fuzz_verify" +path = "fuzz_targets/fuzz_verify.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_sign" +path = "fuzz_targets/fuzz_sign.rs" +test = false +doc = false diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..5a5eed2 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,42 @@ +This is a work in progress, but good results currently. + +Harness code is in fuzz/fuzz_targets/*. The Cargo.toml file specifies that overflow-checks and +debug-assertions are enabled (so the fuzzer can find these panics). + +See + +~~~ +$ cd fuzz # this directory; you may need to install cargo fuzz +$ rustup default nightly +$ mkdir -p corpus/fuzz_sign +$ dd if=/dev/zero bs=1 count=6292 > corpus/fuzz_sign/seed0 +$ for i in $(seq 1 9); do head -c 6292 corpus/fuzz_sign/seed$i; done +$ mkdir -p corpus/fuzz_verify +$ dd if=/dev/zero bs=1 count=6292 > corpus/fuzz_verify/seed0 +$ for i in $(seq 0 9); do head -c 6292 corpus/fuzz_verify/seed$i; done + +$ cargo fuzz run fuzz_sign -j 4 -- -max_total_time=1000 + +... +#205: cov: 2486 ft: 4394 corp: 10 exec/s: 0 oom/timeout/crash: 0/0/0 time: 965s job: 51 dft_time: 0 +#209: cov: 2486 ft: 4394 corp: 10 exec/s: 0 oom/timeout/crash: 0/0/0 time: 965s job: 49 dft_time: 0 +#215: cov: 2486 ft: 4394 corp: 10 exec/s: 0 oom/timeout/crash: 0/0/0 time: 980s job: 52 dft_time: 0 +#219: cov: 2486 ft: 4394 corp: 10 exec/s: 0 oom/timeout/crash: 0/0/0 time: 1020s job: 54 dft_time: 0 +INFO: fuzzed for 1020 seconds, wrapping up soon +INFO: exiting: 0 time: 1050s + + + +$ cargo fuzz run fuzz_verify -j 4 -- -max_total_time=1000 + +... +#307: cov: 18818 ft: 12996 corp: 30 exec/s 0 oom/timeout/crash: 0/0/0 time: 915s job: 57 dft_time: 0 +#314: cov: 18818 ft: 13023 corp: 32 exec/s 0 oom/timeout/crash: 0/0/0 time: 934s job: 58 dft_time: 0 +#321: cov: 18818 ft: 13040 corp: 33 exec/s 0 oom/timeout/crash: 0/0/0 time: 945s job: 59 dft_time: 0 +#328: cov: 18818 ft: 13063 corp: 34 exec/s 0 oom/timeout/crash: 0/0/0 time: 964s job: 60 dft_time: 0 +#336: cov: 18818 ft: 13078 corp: 35 exec/s 0 oom/timeout/crash: 0/0/0 time: 998s job: 61 dft_time: 0 +INFO: fuzzed for 1018 seconds, wrapping up soon +INFO: exiting: 0 time: 1031s +~~~ + +Coverage status is a work-in-progress (note that verify also exercises signing); see FIPS 204 code for example runs diff --git a/fuzz/fuzz_targets/fuzz_sign.rs b/fuzz/fuzz_targets/fuzz_sign.rs new file mode 100644 index 0000000..9df061b --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_sign.rs @@ -0,0 +1,53 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use fips205::{ + slh_dsa_sha2_128f, // Using sha2_128f as an example parameter set + traits::Signer, + Ph, +}; +use rand_core::OsRng; + +// Wrapper struct to help organize the fuzz input +#[derive(arbitrary::Arbitrary, Debug)] +struct FuzzInput { + message: Vec, + context: Vec, + hedged: bool, + use_hash: bool, + hash_function: u8, // We'll map this to Ph variants +} + +fuzz_target!(|input: FuzzInput| { + // Generate a keypair first (using real RNG for this part) + if let Ok((_, sk)) = slh_dsa_sha2_128f::try_keygen() { + // Map the hash function input to actual Ph variants + let ph = match input.hash_function % 3 { + 0 => Ph::SHA256, + 1 => Ph::SHA512, + _ => Ph::SHAKE256, + }; + + // Test regular signing + let _ = sk.try_sign_with_rng( + &mut OsRng, + &input.message, + &input.context[..input.context.len() % 255], + input.hedged + ); + + // Test hash signing + if input.use_hash { + let _ = sk.try_hash_sign_with_rng( + &mut OsRng, + &input.message, + &input.context[..input.context.len() % 255], + &ph, + input.hedged + ); + } + + // Test public key derivation + let _pk = sk.get_public_key(); + } +}); + diff --git a/fuzz/fuzz_targets/fuzz_verify.rs b/fuzz/fuzz_targets/fuzz_verify.rs new file mode 100644 index 0000000..27c316e --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_verify.rs @@ -0,0 +1,50 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use fips205::{ + slh_dsa_sha2_128f, // Using slh_dsa_sha2_128f as example, could test other parameter sets + traits::{SerDes, Signer, Verifier}, + Ph, +}; + +fuzz_target!(|data: &[u8]| { + // Need at least some bytes for our test cases + if data.len() < 32 { + return; + } + + // Generate a valid key pair first + if let Ok((pk, sk)) = slh_dsa_sha2_128f::try_keygen() { + // Split fuzz data into message and context + let split_point = data.len() % 255; + let message = &data[split_point..]; + let context = &data[..split_point]; + + // Test 1: Regular verification with valid signature + if let Ok(valid_sig) = sk.try_sign(message, context, true) { + let _ = pk.verify(message, &valid_sig, context); + } + + // Test 2: Hash verification with valid signature + if let Ok(valid_hash_sig) = sk.try_hash_sign(message, context, &Ph::SHA256, true) { + let _ = pk.hash_verify(message, &valid_hash_sig, context, &Ph::SHA256); + } + + // Test 3: Try to deserialize and verify with potentially malformed public key + if let Ok(maybe_pk) = slh_dsa_sha2_128f::PublicKey::try_from_bytes( + &pk.clone().into_bytes() // Use valid key bytes but could use fuzzed data instead + ) { + if let Ok(sig) = sk.try_sign(message, context, true) { + let _ = maybe_pk.verify(message, &sig, context); + } + } + + // Test 4: Verify with modified message + if let Ok(sig) = sk.try_sign(message, context, true) { + let mut modified_message = message.to_vec(); + if !modified_message.is_empty() { + modified_message[0] ^= 1; // Flip one bit + let _ = pk.verify(&modified_message, &sig, context); + } + } + } +}); diff --git a/src/lib.rs b/src/lib.rs index 6e83827..ecab6df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,6 +402,12 @@ macro_rules! functionality { let sig = sk2.try_sign_with_rng(&mut rng, &message, b"context", true).unwrap(); let result = pk2.verify(&message, &sig, b"context"); + assert!(result, "Signature failed to verify"); + + let (pk3, sk3) = KG::keygen_with_seeds(&[0u8; N], &[1u8; N], &[2u8; N]); + let sig = sk3.try_sign_with_rng(&mut rng, &message, b"context", true).unwrap(); + let result = pk3.verify(&message, &sig, b"context"); + assert!(result, "Signature failed to verify"); let result = pk2.verify(&message, &sig, b"some other context"); assert!(!result, "Signature should not have verified"); @@ -420,9 +426,10 @@ macro_rules! functionality { } -/// Functionality for the **SLH-DSA-SHA2-128s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-128s parameter set is claimed to be in security strength category 1. +/// Functionality for the **SLH-DSA-SHA2-128s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-128s parameter set is claimed to be in security strength category 1. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_128s::try_keygen`] function below /// to generate both [`slh_dsa_sha2_128s::PublicKey`] and [`slh_dsa_sha2_128s::PrivateKey`] structs. The resulting @@ -443,7 +450,8 @@ pub mod slh_dsa_sha2_128s { use crate::hashers::sha2_cat_1::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 16; + /// Seed size + pub const N: usize = 16; const H: usize = 63; const D: usize = 7; const HP: usize = 9; @@ -468,9 +476,10 @@ pub mod slh_dsa_sha2_128s { } -/// Functionality for the **SLH-DSA-SHAKE-128s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE-128s parameter set is claimed to be in security strength category 1. +/// Functionality for the **SLH-DSA-SHAKE-128s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE-128s parameter set is claimed to be in security strength category 1. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_128s::try_keygen`] function below /// to generate both [`slh_dsa_shake_128s::PublicKey`] and [`slh_dsa_shake_128s::PrivateKey`] structs. The resulting @@ -491,7 +500,8 @@ pub mod slh_dsa_shake_128s { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 16; + /// Seed size + pub const N: usize = 16; const H: usize = 63; const D: usize = 7; const HP: usize = 9; @@ -516,9 +526,10 @@ pub mod slh_dsa_shake_128s { } -/// Functionality for the **SLH-DSA-SHA2-128f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-128f parameter set is claimed to be in security strength category 1. +/// Functionality for the **SLH-DSA-SHA2-128f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-128f parameter set is claimed to be in security strength category 1. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_128f::try_keygen`] function below /// to generate both [`slh_dsa_sha2_128f::PublicKey`] and [`slh_dsa_sha2_128f::PrivateKey`] structs. The resulting @@ -539,7 +550,8 @@ pub mod slh_dsa_sha2_128f { use crate::hashers::sha2_cat_1::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 16; + /// Seed size + pub const N: usize = 16; const H: usize = 66; const D: usize = 22; const HP: usize = 3; @@ -564,9 +576,10 @@ pub mod slh_dsa_sha2_128f { } -/// Functionality for the **SLH-DSA-SHAKE-128f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE-128f parameter set is claimed to be in security strength category 1. +/// Functionality for the **SLH-DSA-SHAKE-128f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE-128f parameter set is claimed to be in security strength category 1. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_128f::try_keygen`] function below /// to generate both [`slh_dsa_shake_128f::PublicKey`] and [`slh_dsa_shake_128f::PrivateKey`] structs. The resulting @@ -587,7 +600,8 @@ pub mod slh_dsa_shake_128f { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 16; + /// Seed size + pub const N: usize = 16; const H: usize = 66; const D: usize = 22; const HP: usize = 3; @@ -612,9 +626,10 @@ pub mod slh_dsa_shake_128f { } -/// Functionality for the **SLH-DSA-SHA2-192s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-192s parameter set is claimed to be in security strength category 3. +/// Functionality for the **SLH-DSA-SHA2-192s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-192s parameter set is claimed to be in security strength category 3. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_192s::try_keygen`] function below /// to generate both [`slh_dsa_sha2_192s::PublicKey`] and [`slh_dsa_sha2_192s::PrivateKey`] structs. The resulting @@ -635,7 +650,8 @@ pub mod slh_dsa_sha2_192s { use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 24; + /// Seed size + pub const N: usize = 24; const H: usize = 63; const D: usize = 7; const HP: usize = 9; @@ -660,9 +676,10 @@ pub mod slh_dsa_sha2_192s { } -/// Functionality for the **SLH-DSA-SHAKE-192s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE-192s parameter set is claimed to be in security strength category 3. +/// Functionality for the **SLH-DSA-SHAKE-192s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE-192s parameter set is claimed to be in security strength category 3. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_192s::try_keygen`] function below /// to generate both [`slh_dsa_shake_192s::PublicKey`] and [`slh_dsa_shake_192s::PrivateKey`] structs. The resulting @@ -683,7 +700,8 @@ pub mod slh_dsa_shake_192s { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 24; + /// Seed size + pub const N: usize = 24; const H: usize = 63; const D: usize = 7; const HP: usize = 9; @@ -708,9 +726,10 @@ pub mod slh_dsa_shake_192s { } -/// Functionality for the **SLH-DSA-SHA2-192f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-192f parameter set is claimed to be in security strength category 3. +/// Functionality for the **SLH-DSA-SHA2-192f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-192f parameter set is claimed to be in security strength category 3. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_192f::try_keygen`] function below /// to generate both [`slh_dsa_sha2_192f::PublicKey`] and [`slh_dsa_sha2_192f::PrivateKey`] structs. The resulting @@ -731,7 +750,8 @@ pub mod slh_dsa_sha2_192f { use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 24; + /// Seed size + pub const N: usize = 24; const H: usize = 66; const D: usize = 22; const HP: usize = 3; @@ -756,9 +776,10 @@ pub mod slh_dsa_sha2_192f { } -/// Functionality for the **SLH-DSA-SHAKE-192f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE-192f parameter set is claimed to be in security strength category 3. +/// Functionality for the **SLH-DSA-SHAKE-192f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE-192f parameter set is claimed to be in security strength category 3. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_192f::try_keygen`] function below /// to generate both [`slh_dsa_shake_192f::PublicKey`] and [`slh_dsa_shake_192f::PrivateKey`] structs. The resulting @@ -779,7 +800,8 @@ pub mod slh_dsa_shake_192f { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 24; + /// Seed size + pub const N: usize = 24; const H: usize = 66; const D: usize = 22; const HP: usize = 3; @@ -804,9 +826,10 @@ pub mod slh_dsa_shake_192f { } -/// Functionality for the **SLH-DSA-SHA2-256s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-256s parameter set is claimed to be in security strength category 5. +/// Functionality for the **SLH-DSA-SHA2-256s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-256s parameter set is claimed to be in security strength category 5. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_256s::try_keygen`] function below /// to generate both [`slh_dsa_sha2_256s::PublicKey`] and [`slh_dsa_sha2_256s::PrivateKey`] structs. The resulting @@ -827,7 +850,8 @@ pub mod slh_dsa_sha2_256s { use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 32; + /// Seed size + pub const N: usize = 32; const H: usize = 64; const D: usize = 8; const HP: usize = 8; @@ -852,9 +876,10 @@ pub mod slh_dsa_sha2_256s { } -/// Functionality for the **SLH-DSA-SHAKE-256s** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE_256s parameter set is claimed to be in security strength category 5. +/// Functionality for the **SLH-DSA-SHAKE-256s** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE_256s parameter set is claimed to be in security strength category 5. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_256s::try_keygen`] function below /// to generate both [`slh_dsa_shake_256s::PublicKey`] and [`slh_dsa_shake_256s::PrivateKey`] structs. The resulting @@ -875,7 +900,8 @@ pub mod slh_dsa_shake_256s { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 32; + /// Seed size + pub const N: usize = 32; const H: usize = 64; const D: usize = 8; const HP: usize = 8; @@ -900,9 +926,10 @@ pub mod slh_dsa_shake_256s { } -/// Functionality for the **SLH-DSA-SHA2-256f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHA2-256f parameter set is claimed to be in security strength category 5. +/// Functionality for the **SLH-DSA-SHA2-256f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHA2-256f parameter set is claimed to be in security strength category 5. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_sha2_256f::try_keygen`] function below /// to generate both [`slh_dsa_sha2_256f::PublicKey`] and [`slh_dsa_sha2_256f::PrivateKey`] structs. The resulting @@ -923,7 +950,8 @@ pub mod slh_dsa_sha2_256f { use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 32; + /// Seed size + pub const N: usize = 32; const H: usize = 68; const D: usize = 17; const HP: usize = 4; @@ -948,9 +976,10 @@ pub mod slh_dsa_sha2_256f { } -/// Functionality for the **SLH-DSA-SHAKE-256f** security parameter set per FIPS 205 section 11. This includes specific -/// sizes for the public key, secret key, and signature along with a number of internal constants. The -/// SLH-DSA-SHAKE-256f parameter set is claimed to be in security strength category 5. +/// Functionality for the **SLH-DSA-SHAKE-256f** security parameter set per FIPS 205 section 11. +/// +/// This includes specific sizes for the public key, secret key, and signature along with a number of internal +/// constants. The SLH-DSA-SHAKE-256f parameter set is claimed to be in security strength category 5. /// /// **1)** The basic usage is for an originator to start with the [`slh_dsa_shake_256f::try_keygen`] function below /// to generate both [`slh_dsa_shake_256f::PublicKey`] and [`slh_dsa_shake_256f::PrivateKey`] structs. The resulting @@ -971,7 +1000,8 @@ pub mod slh_dsa_shake_256f { use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l}; use crate::hashers::Hashers; - const N: usize = 32; + /// Seed size + pub const N: usize = 32; const H: usize = 68; const D: usize = 17; const HP: usize = 4; diff --git a/src/traits.rs b/src/traits.rs index c21c9f5..254358d 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,6 @@ use rand_core::CryptoRngCore; +use rand_core::RngCore; +use rand_core::CryptoRng; use crate::Ph; #[cfg(feature = "default-rng")] @@ -99,8 +101,75 @@ pub trait KeyGen { fn try_keygen_with_rng( rng: &mut impl CryptoRngCore, ) -> Result<(Self::PublicKey, Self::PrivateKey), &'static str>; + + /// Generates a public and private key pair specific to this security parameter set. + /// This function utilizes **three provided seeds** rather than a random number + /// generator in order to deterministically generate keys. This function operates + /// in constant-time relative to secret data. + /// # Errors + /// Returns an error when the random number generator fails. + /// # Examples + /// ```rust + /// use fips205::slh_dsa_shake_128s; // Could use any of the twelve security parameter sets. + /// use fips205::traits::{KeyGen, SerDes, Signer, Verifier}; + /// # use std::error::Error; + /// # use rand_core::OsRng; + /// # + /// # fn main() -> Result<(), Box> { + /// + /// let msg_bytes = [0u8, 1, 2, 3, 4, 5, 6, 7]; + /// let mut rng = OsRng; + /// + /// // Generate both public and secret keys. This only fails when the provided rng fails. + /// let (pk1, sk) = slh_dsa_shake_128s::KG::keygen_with_seeds(&[0u8; slh_dsa_shake_128s::N], + /// &[1u8; slh_dsa_shake_128s::N], &[1u8; slh_dsa_shake_128s::N]); + /// // Use the secret key to generate a signature. The second parameter is the + /// // context string (often just an empty &[]), and the last parameter selects + /// // the preferred hedged variant. This only fails when the OS rng fails. + /// let sig_bytes = sk.try_sign(&msg_bytes, b"context", true)?; + /// + /// + /// // Serialize the public key, and send with message and signature bytes. These + /// // statements model sending byte arrays over the wire. + /// let (pk_send, msg_send, sig_send) = (pk1.into_bytes(), msg_bytes, sig_bytes); + /// let (pk_recv, msg_recv, sig_recv) = (pk_send, msg_send, sig_send); + /// + /// + /// // Deserialize the public key. This only fails on a malformed key. + /// let pk2 = slh_dsa_shake_128s::PublicKey::try_from_bytes(&pk_recv)?; + /// // Use the public key to verify the msg signature + /// let v = pk2.verify(&msg_recv, &sig_recv, b"context"); + /// assert!(v); + /// # Ok(()) + /// # } + /// ``` + fn keygen_with_seeds( + sk_seed: &[u8; N], sk_prf: &[u8; N], pk_seed: &[u8; N] + ) -> (Self::PublicKey, Self::PrivateKey) { + Self::try_keygen_with_rng(&mut DummyRng {data: [*sk_seed, *sk_prf, *pk_seed], i: 0 }).expect("rng will not fail") + } + } +// This is for the deterministic keygen functions; will be refactored more nicely +struct DummyRng { data: [[u8; N]; 3], i: usize } + +impl RngCore for DummyRng { + fn next_u32(&mut self) -> u32 { unimplemented!() } + + fn next_u64(&mut self) -> u64 { unimplemented!() } + + fn fill_bytes(&mut self, _out: &mut [u8]) { unimplemented!() } + + fn try_fill_bytes(&mut self, out: &mut [u8]) -> Result<(), rand_core::Error> { + out.copy_from_slice(&self.data[self.i]); + self.i += 1; + Ok(()) + } +} + +impl CryptoRng for DummyRng {} + /// The Signer trait is implemented for the `PrivateKey` struct on each of the security parameter sets pub trait Signer {