Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 3 additions & 50 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "multi-key"
version = "1.1.1"
edition = "2024"
rust-version = "1.87"
rust-version = "1.95"
authors = ["Dave Grantham <dwg@linuxprogrammer.org>"]
description = "Multikey self-describing cryptographic key data"
repository = "https://github.com/cryptidtech/multi-key.git"
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/keysplit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/mk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3564,6 +3564,7 @@ mod tests {
.unwrap();
}

#[cfg(feature = "slow-tests")]
#[test]
fn test_slh_dsa_random() {
for codec in SLH_DSA_KEY_CODECS {
Expand Down Expand Up @@ -3594,6 +3595,7 @@ mod tests {
}
}

#[cfg(feature = "slow-tests")]
#[test]
fn test_slh_dsa_encoded_random() {
for codec in SLH_DSA_KEY_CODECS {
Expand All @@ -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+).
Expand Down Expand Up @@ -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+).
Expand Down
8 changes: 4 additions & 4 deletions src/views/bls12381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok(b) = Varuint::<bool>::try_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
8 changes: 4 additions & 4 deletions src/views/chacha20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
1 change: 1 addition & 0 deletions src/views/classic_mceliece.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ mod tests {
}
}

#[cfg(feature = "slow-tests")]
#[test]
fn test_mceliece_public_key_derivation() {
for codec in MCELIECE_KEY_CODECS {
Expand Down
8 changes: 4 additions & 4 deletions src/views/dkg_threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
8 changes: 4 additions & 4 deletions src/views/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
8 changes: 4 additions & 4 deletions src/views/nist_p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
8 changes: 4 additions & 4 deletions src/views/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
8 changes: 4 additions & 4 deletions src/views/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bool>::try_decode_from(v.as_slice()) {
return b.to_inner();
}
if let Some(v) = self.mk.attributes.get(&AttrId::KeyIsEncrypted)
&& let Ok((b, _)) = Varuint::<bool>::try_decode_from(v.as_slice())
{
return b.to_inner();
}
false
}
Expand Down
4 changes: 4 additions & 0 deletions src/views/xmss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,12 @@ pub(crate) fn generate_private_key(codec: Codec) -> Result<Zeroizing<Vec<u8>>, 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).
Expand Down Expand Up @@ -422,6 +425,7 @@ mod tests {
);
}

#[cfg(feature = "slow-tests")]
#[test]
fn test_xmss_sign_advance_persists_index() {
let mut rng = rand::rng();
Expand Down
10 changes: 5 additions & 5 deletions tests/edge_case_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
24 changes: 12 additions & 12 deletions tests/proptest_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = 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<u8> = 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());
}
}
});
Expand All @@ -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);
}
});
}
Loading