diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 335e9c7..98ef144 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,66 +10,19 @@ env: CARGO_TERM_COLOR: always jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install Rust Toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - - - name: Check Code Format - run: cargo fmt --all -- --check - - - name: Code Lint - run: cargo clippy --all-targets --all-features -- -D warnings - - - name: Build - run: cargo build --verbose --all-features - - - name: Run tests - run: cargo test --verbose --all-features - msrv: - name: Verify MSRV (1.85) + name: Verify MSRV (1.95) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install Rust Toolchain (1.85) - uses: dtolnay/rust-toolchain@1.85.0 + - name: Install Rust Toolchain (1.95) + uses: dtolnay/rust-toolchain@1.95.0 - name: Check run: cargo check --all-features - coverage: - name: Coverage - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install Rust Toolchain (stable) - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools-preview - - - name: Install cargo-llvm-cov - run: cargo install cargo-llvm-cov - - - name: Run coverage - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - files: lcov.info - fail_ci_if_error: false - audit: name: Security Audit runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 84e8b1a..4c45278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "multi-key" version = "1.1.1" edition = "2024" -rust-version = "1.87" +rust-version = "1.95" authors = ["Dave Grantham "] description = "Multikey self-describing cryptographic key data" repository = "https://github.com/cryptidtech/multi-key.git" @@ -21,6 +21,10 @@ wasm = ["getrandom/wasm_js"] legacy_chacha20_fallback = [] lamport = ["dep:lamport_signature_plus", "dep:sha3", "dep:blake2", "dep:shake"] xmss = ["dep:xmss"] +# Tests that take over 60 seconds in debug builds (SLH-DSA, Classic McEliece, +# XMSS). Excluded from the default `cargo test` run; enable with +# `cargo test --features slow-tests` to run the full suite. +slow-tests = [] [dependencies] aes-gcm = "0.11" diff --git a/src/keysplit.rs b/src/keysplit.rs index b289a5d..923cb0d 100644 --- a/src/keysplit.rs +++ b/src/keysplit.rs @@ -609,6 +609,7 @@ mod tests { assert_all(&mk::MAYO_KEY_CODECS); } + #[cfg(feature = "slow-tests")] #[test] fn roundtrip_slh_dsa() { assert_all(&mk::SLH_DSA_KEY_CODECS); diff --git a/src/mk.rs b/src/mk.rs index 2d6ba71..373751f 100644 --- a/src/mk.rs +++ b/src/mk.rs @@ -3564,6 +3564,7 @@ mod tests { .unwrap(); } + #[cfg(feature = "slow-tests")] #[test] fn test_slh_dsa_random() { for codec in SLH_DSA_KEY_CODECS { @@ -3594,6 +3595,7 @@ mod tests { } } + #[cfg(feature = "slow-tests")] #[test] fn test_slh_dsa_encoded_random() { for codec in SLH_DSA_KEY_CODECS { @@ -3609,6 +3611,7 @@ mod tests { } } + #[cfg(feature = "slow-tests")] #[test] fn test_slh_dsa_signing_detached_roundtrip() { // SLH-DSA signing uses large stack buffers internally (Merkle trees, WOTS+). @@ -3651,6 +3654,7 @@ mod tests { .unwrap(); } + #[cfg(feature = "slow-tests")] #[test] fn test_slh_dsa_signing_merged_roundtrip() { // SLH-DSA signing uses large stack buffers internally (Merkle trees, WOTS+). diff --git a/src/views/bls12381.rs b/src/views/bls12381.rs index d7d1efb..04d589b 100644 --- a/src/views/bls12381.rs +++ b/src/views/bls12381.rs @@ -192,10 +192,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok(b) = Varuint::::try_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok(b) = Varuint::::try_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/chacha20.rs b/src/views/chacha20.rs index 9dfacc1..cdafb6e 100644 --- a/src/views/chacha20.rs +++ b/src/views/chacha20.rs @@ -49,10 +49,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/classic_mceliece.rs b/src/views/classic_mceliece.rs index 54abd0c..8554704 100644 --- a/src/views/classic_mceliece.rs +++ b/src/views/classic_mceliece.rs @@ -285,6 +285,7 @@ mod tests { } } + #[cfg(feature = "slow-tests")] #[test] fn test_mceliece_public_key_derivation() { for codec in MCELIECE_KEY_CODECS { diff --git a/src/views/dkg_threshold.rs b/src/views/dkg_threshold.rs index 1c479d1..04275fb 100644 --- a/src/views/dkg_threshold.rs +++ b/src/views/dkg_threshold.rs @@ -36,10 +36,10 @@ impl<'a> AttrView for View<'a> { // meant callers using `threshold_attr_view()` alone saw an unencrypted // share even when it was sealed. Read the attribute directly so the // status is authoritative regardless of which view the caller holds. - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/ed25519.rs b/src/views/ed25519.rs index 90c2887..7d37d85 100644 --- a/src/views/ed25519.rs +++ b/src/views/ed25519.rs @@ -28,10 +28,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/nist_p.rs b/src/views/nist_p.rs index be34374..6ebcbb5 100644 --- a/src/views/nist_p.rs +++ b/src/views/nist_p.rs @@ -59,10 +59,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/rsa.rs b/src/views/rsa.rs index 5ce91ee..a61e4f8 100644 --- a/src/views/rsa.rs +++ b/src/views/rsa.rs @@ -124,10 +124,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/secp256k1.rs b/src/views/secp256k1.rs index c9e6dd4..0e2efdb 100644 --- a/src/views/secp256k1.rs +++ b/src/views/secp256k1.rs @@ -43,10 +43,10 @@ impl<'a> TryFrom<&'a Multikey> for View<'a> { impl<'a> AttrView for View<'a> { fn is_encrypted(&self) -> bool { - if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) { - if let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) { - return b.to_inner(); - } + if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted) + && let Ok((b, _)) = Varuint::::try_decode_from(v.as_slice()) + { + return b.to_inner(); } false } diff --git a/src/views/xmss.rs b/src/views/xmss.rs index 84f8083..562679f 100644 --- a/src/views/xmss.rs +++ b/src/views/xmss.rs @@ -391,9 +391,12 @@ pub(crate) fn generate_private_key(codec: Codec) -> Result>, E #[cfg(test)] mod tests { + #[cfg(feature = "slow-tests")] use super::*; + #[cfg(feature = "slow-tests")] use crate::Builder; + #[cfg(feature = "slow-tests")] #[test] fn test_xmss_sign_verify_and_index() { // Only h=10 is exercised: h=16/20 keygen builds a 2^h Merkle tree (minutes). @@ -422,6 +425,7 @@ mod tests { ); } + #[cfg(feature = "slow-tests")] #[test] fn test_xmss_sign_advance_persists_index() { let mut rng = rand::rng(); diff --git a/tests/edge_case_tests.rs b/tests/edge_case_tests.rs index bd1bfa9..e694c2d 100644 --- a/tests/edge_case_tests.rs +++ b/tests/edge_case_tests.rs @@ -29,11 +29,11 @@ fn test_all_key_codecs() { #[test] fn test_clone() { let mut rng = rand::rng(); - if let Ok(builder) = Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rng) { - if let Ok(mk1) = builder.try_build() { - let mk2 = mk1.clone(); - assert_eq!(mk1, mk2); - } + if let Ok(builder) = Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rng) + && let Ok(mk1) = builder.try_build() + { + let mk2 = mk1.clone(); + assert_eq!(mk1, mk2); } } diff --git a/tests/proptest_tests.rs b/tests/proptest_tests.rs index 635f383..751ba20 100644 --- a/tests/proptest_tests.rs +++ b/tests/proptest_tests.rs @@ -12,14 +12,14 @@ fn test_multikey_roundtrip() { proptest!(|(_unit in 0..1u8)| { let mut rng = rand::rng(); for &codec in KEY_CODECS.iter().take(2) { - if let Ok(mk1) = Builder::new_from_random_bytes(codec, &mut rng) { - if let Ok(mk1) = mk1.try_build() { - let bytes: Vec = mk1.clone().into(); - let (mk2, remaining) = Multikey::try_decode_from(&bytes).unwrap(); + if let Ok(mk1) = Builder::new_from_random_bytes(codec, &mut rng) + && let Ok(mk1) = mk1.try_build() + { + let bytes: Vec = mk1.clone().into(); + let (mk2, remaining) = Multikey::try_decode_from(&bytes).unwrap(); - prop_assert_eq!(&mk1, &mk2); - prop_assert!(remaining.is_empty()); - } + prop_assert_eq!(&mk1, &mk2); + prop_assert!(remaining.is_empty()); } } }); @@ -30,11 +30,11 @@ fn test_multikey_roundtrip() { fn test_clone_equality() { proptest!(|(_unit in 0..1u8)| { let mut rng = rand::rng(); - if let Ok(builder) = Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rng) { - if let Ok(mk1) = builder.try_build() { - let mk2 = mk1.clone(); - prop_assert_eq!(&mk1, &mk2); - } + if let Ok(builder) = Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rng) + && let Ok(mk1) = builder.try_build() + { + let mk2 = mk1.clone(); + prop_assert_eq!(&mk1, &mk2); } }); }