use crate::error::{Result, ShamirError};
use crate::shamir::{ShamirShare, Share};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
pub struct AccessLevel {
pub name: String,
pub shares_count: u8,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
pub struct HierarchicalShare {
pub level_name: String,
pub shares: Vec<Share>,
}
#[derive(Debug)]
pub struct Hsss {
master_scheme: ShamirShare,
levels: Vec<AccessLevel>,
}
#[derive(Debug)]
pub struct HsssBuilder {
master_threshold: u8,
levels: Vec<AccessLevel>,
}
impl HsssBuilder {
pub fn new(master_threshold: u8) -> Self {
Self {
master_threshold,
levels: Vec::new(),
}
}
pub fn add_level(mut self, name: &str, shares_count: u8) -> Self {
self.levels.push(AccessLevel {
name: name.to_string(),
shares_count,
});
self
}
pub fn build(self) -> Result<Hsss> {
if self.master_threshold == 0 {
return Err(ShamirError::InvalidThreshold(self.master_threshold));
}
if self.levels.is_empty() {
return Err(ShamirError::InvalidConfig(
"At least one access level must be defined".to_string(),
));
}
for level in &self.levels {
if level.shares_count == 0 {
return Err(ShamirError::InvalidShareCount(level.shares_count));
}
}
let total_shares: u32 = self.levels.iter().map(|level| level.shares_count as u32).sum();
if total_shares == 0 {
return Err(ShamirError::InvalidShareCount(0));
}
if total_shares > 255 {
return Err(ShamirError::InvalidConfig(format!(
"Total shares count {} exceeds maximum of 255",
total_shares
)));
}
let n_master = total_shares as u8;
if self.master_threshold > n_master {
return Err(ShamirError::ThresholdTooLarge {
threshold: self.master_threshold,
total_shares: n_master,
});
}
let master_scheme = ShamirShare::builder(n_master, self.master_threshold).build()?;
Ok(Hsss {
master_scheme,
levels: self.levels,
})
}
}
impl Hsss {
pub fn builder(master_threshold: u8) -> HsssBuilder {
HsssBuilder::new(master_threshold)
}
pub fn levels(&self) -> &[AccessLevel] {
&self.levels
}
pub fn master_threshold(&self) -> u8 {
self.master_scheme.threshold()
}
pub fn total_shares(&self) -> u8 {
self.master_scheme.total_shares()
}
pub fn split_secret(&mut self, secret: &[u8]) -> Result<Vec<HierarchicalShare>> {
let all_master_shares = self.master_scheme.split(secret)?;
let mut share_iter = all_master_shares.into_iter();
let mut hierarchical_shares = Vec::with_capacity(self.levels.len());
for level in &self.levels {
let mut shares = Vec::with_capacity(level.shares_count as usize);
for _ in 0..level.shares_count {
let share = share_iter.next().ok_or_else(|| {
ShamirError::InvalidConfig(format!(
"Insufficient master shares available for level '{}': this should not happen",
level.name
))
})?;
shares.push(share);
}
let hierarchical_share = HierarchicalShare {
level_name: level.name.clone(),
shares,
};
hierarchical_shares.push(hierarchical_share);
}
Ok(hierarchical_shares)
}
pub fn reconstruct(&self, hierarchical_shares: &[HierarchicalShare]) -> Result<Vec<u8>> {
let mut all_shares = Vec::new();
for hierarchical_share in hierarchical_shares {
all_shares.extend_from_slice(&hierarchical_share.shares);
}
ShamirShare::reconstruct(&all_shares)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_access_level_creation() {
let level = AccessLevel {
name: "President".to_string(),
shares_count: 5,
};
assert_eq!(level.name, "President");
assert_eq!(level.shares_count, 5);
}
#[test]
fn test_hierarchical_share_creation() {
let share = HierarchicalShare {
level_name: "VP".to_string(),
shares: vec![],
};
assert_eq!(share.level_name, "VP");
assert_eq!(share.shares.len(), 0);
}
#[test]
fn test_hsss_builder_basic() {
let hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
assert_eq!(hsss.master_threshold(), 5);
assert_eq!(hsss.total_shares(), 10); assert_eq!(hsss.levels().len(), 3);
let levels = hsss.levels();
assert_eq!(levels[0].name, "President");
assert_eq!(levels[0].shares_count, 5);
assert_eq!(levels[1].name, "VP");
assert_eq!(levels[1].shares_count, 3);
assert_eq!(levels[2].name, "Executive");
assert_eq!(levels[2].shares_count, 2);
}
#[test]
fn test_hsss_builder_single_level() {
let hsss = Hsss::builder(3)
.add_level("Admin", 5)
.build()
.unwrap();
assert_eq!(hsss.master_threshold(), 3);
assert_eq!(hsss.total_shares(), 5);
assert_eq!(hsss.levels().len(), 1);
assert_eq!(hsss.levels()[0].name, "Admin");
assert_eq!(hsss.levels()[0].shares_count, 5);
}
#[test]
fn test_hsss_builder_validation_zero_threshold() {
let result = Hsss::builder(0)
.add_level("President", 5)
.build();
assert!(matches!(result, Err(ShamirError::InvalidThreshold(0))));
}
#[test]
fn test_hsss_builder_validation_no_levels() {
let result = Hsss::builder(5).build();
assert!(matches!(result, Err(ShamirError::InvalidConfig(_))));
}
#[test]
fn test_hsss_builder_validation_zero_shares() {
let result = Hsss::builder(5)
.add_level("President", 0)
.build();
assert!(matches!(result, Err(ShamirError::InvalidShareCount(0))));
}
#[test]
fn test_hsss_builder_validation_threshold_too_large() {
let result = Hsss::builder(10)
.add_level("President", 5)
.add_level("VP", 3)
.build();
assert!(matches!(
result,
Err(ShamirError::ThresholdTooLarge { threshold: 10, total_shares: 8 })
));
}
#[test]
fn test_hsss_builder_validation_too_many_shares() {
let result = Hsss::builder(5)
.add_level("Level1", 200)
.add_level("Level2", 100)
.build();
assert!(matches!(result, Err(ShamirError::InvalidConfig(_))));
}
#[test]
fn test_hsss_builder_method_chaining() {
let hsss = Hsss::builder(7)
.add_level("CEO", 7)
.add_level("CTO", 5)
.add_level("Manager", 3)
.add_level("Employee", 1)
.build()
.unwrap();
assert_eq!(hsss.master_threshold(), 7);
assert_eq!(hsss.total_shares(), 16); assert_eq!(hsss.levels().len(), 4);
}
#[test]
fn test_hsss_builder_edge_case_threshold_equals_total() {
let hsss = Hsss::builder(10)
.add_level("President", 5)
.add_level("VP", 5)
.build()
.unwrap();
assert_eq!(hsss.master_threshold(), 10);
assert_eq!(hsss.total_shares(), 10);
}
#[test]
fn test_hsss_builder_max_shares() {
let hsss = Hsss::builder(255)
.add_level("Level1", 255)
.build()
.unwrap();
assert_eq!(hsss.master_threshold(), 255);
assert_eq!(hsss.total_shares(), 255);
}
#[test]
fn test_access_level_clone() {
let level1 = AccessLevel {
name: "President".to_string(),
shares_count: 5,
};
let level2 = level1.clone();
assert_eq!(level1, level2);
assert_eq!(level1.name, level2.name);
assert_eq!(level1.shares_count, level2.shares_count);
}
#[test]
fn test_hierarchical_share_clone() {
let share1 = HierarchicalShare {
level_name: "VP".to_string(),
shares: vec![],
};
let share2 = share1.clone();
assert_eq!(share1, share2);
assert_eq!(share1.level_name, share2.level_name);
assert_eq!(share1.shares.len(), share2.shares.len());
}
#[test]
#[cfg(feature = "zeroize")]
fn test_zeroize_derives() {
use zeroize::Zeroize;
let mut level = AccessLevel {
name: "Secret".to_string(),
shares_count: 5,
};
level.zeroize();
assert_eq!(level.name, "");
assert_eq!(level.shares_count, 0);
let mut hierarchical_share = HierarchicalShare {
level_name: "Secret".to_string(),
shares: vec![],
};
hierarchical_share.zeroize();
assert_eq!(hierarchical_share.level_name, "");
assert_eq!(hierarchical_share.shares.len(), 0);
}
#[test]
fn test_split_secret_basic() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
let secret = b"top secret information";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
assert_eq!(hierarchical_shares.len(), 3);
assert_eq!(hierarchical_shares[0].level_name, "President");
assert_eq!(hierarchical_shares[0].shares.len(), 5);
assert_eq!(hierarchical_shares[1].level_name, "VP");
assert_eq!(hierarchical_shares[1].shares.len(), 3);
assert_eq!(hierarchical_shares[2].level_name, "Executive");
assert_eq!(hierarchical_shares[2].shares.len(), 2);
for hierarchical_share in &hierarchical_shares {
for share in &hierarchical_share.shares {
assert_eq!(share.threshold, 5); assert_eq!(share.total_shares, 10); assert!(share.integrity_check); }
}
}
#[test]
fn test_reconstruct_president_alone() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
let secret = b"classified data";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let reconstructed = hsss.reconstruct(&hierarchical_shares[0..1]).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_reconstruct_vp_and_executive() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
let secret = b"sensitive information";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let reconstructed = hsss.reconstruct(&hierarchical_shares[1..3]).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_reconstruct_all_levels() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
let secret = b"multi-level secret";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let reconstructed = hsss.reconstruct(&hierarchical_shares).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_reconstruct_insufficient_shares() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5)
.add_level("VP", 3)
.add_level("Executive", 2)
.build()
.unwrap();
let secret = b"protected data";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let result = hsss.reconstruct(&hierarchical_shares[1..2]);
assert!(matches!(result, Err(ShamirError::InsufficientShares { needed: 5, got: 3 })));
let result = hsss.reconstruct(&hierarchical_shares[2..3]);
assert!(matches!(result, Err(ShamirError::InsufficientShares { needed: 5, got: 2 })));
}
#[test]
fn test_split_secret_single_level() {
let mut hsss = Hsss::builder(3)
.add_level("Admin", 5)
.build()
.unwrap();
let secret = b"admin secret";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
assert_eq!(hierarchical_shares.len(), 1);
assert_eq!(hierarchical_shares[0].level_name, "Admin");
assert_eq!(hierarchical_shares[0].shares.len(), 5);
let reconstructed = hsss.reconstruct(&hierarchical_shares).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_split_secret_empty_secret() {
let mut hsss = Hsss::builder(2)
.add_level("Level1", 3)
.add_level("Level2", 2)
.build()
.unwrap();
let secret = b"";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
assert_eq!(hierarchical_shares.len(), 2);
let reconstructed = hsss.reconstruct(&hierarchical_shares).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_split_secret_large_secret() {
let mut hsss = Hsss::builder(10)
.add_level("CEO", 10)
.add_level("CTO", 7)
.add_level("Manager", 5)
.add_level("Employee", 3)
.build()
.unwrap();
let secret: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
let hierarchical_shares = hsss.split_secret(&secret).unwrap();
assert_eq!(hierarchical_shares.len(), 4);
assert_eq!(hierarchical_shares[0].shares.len(), 10); assert_eq!(hierarchical_shares[1].shares.len(), 7); assert_eq!(hierarchical_shares[2].shares.len(), 5); assert_eq!(hierarchical_shares[3].shares.len(), 3);
let reconstructed = hsss.reconstruct(&hierarchical_shares[0..1]).unwrap();
assert_eq!(reconstructed, secret);
let reconstructed = hsss.reconstruct(&hierarchical_shares[1..3]).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_split_secret_different_combinations() {
let mut hsss = Hsss::builder(7)
.add_level("Level1", 7)
.add_level("Level2", 4)
.add_level("Level3", 3)
.add_level("Level4", 2)
.build()
.unwrap();
let secret = b"combination test secret";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let valid_combinations = vec![
vec![0], vec![1, 2], vec![0, 1], vec![1, 2, 3], ];
for combo in valid_combinations {
let mut selected_shares = Vec::new();
for &level_idx in &combo {
if level_idx < hierarchical_shares.len() {
selected_shares.push(hierarchical_shares[level_idx].clone());
}
}
let reconstructed = hsss.reconstruct(&selected_shares).unwrap();
assert_eq!(reconstructed, secret);
}
}
#[test]
fn test_reconstruct_no_hierarchical_shares() {
let hsss = Hsss::builder(5)
.add_level("President", 5)
.build()
.unwrap();
let result = hsss.reconstruct(&[]);
assert!(matches!(result, Err(ShamirError::InsufficientShares { needed: 1, got: 0 })));
}
#[test]
fn test_share_indices_are_unique() {
let mut hsss = Hsss::builder(5)
.add_level("Level1", 3)
.add_level("Level2", 4)
.add_level("Level3", 2)
.build()
.unwrap();
let secret = b"unique indices test";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
let mut all_indices = Vec::new();
for hierarchical_share in &hierarchical_shares {
for share in &hierarchical_share.shares {
all_indices.push(share.index);
}
}
all_indices.sort();
for i in 1..all_indices.len() {
assert_ne!(all_indices[i-1], all_indices[i], "Found duplicate share index: {}", all_indices[i]);
}
assert_eq!(all_indices[0], 1);
assert_eq!(all_indices[all_indices.len() - 1], hsss.total_shares());
}
#[test]
fn test_split_secret_with_integrity_disabled() {
use crate::config::Config;
let config = Config::new().with_integrity_check(false);
let master_scheme = ShamirShare::builder(10, 5)
.with_config(config)
.build()
.unwrap();
let mut hsss = Hsss {
master_scheme,
levels: vec![
AccessLevel { name: "Admin".to_string(), shares_count: 6 },
AccessLevel { name: "User".to_string(), shares_count: 4 },
],
};
let secret = b"no integrity check";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
for hierarchical_share in &hierarchical_shares {
for share in &hierarchical_share.shares {
assert!(!share.integrity_check);
}
}
let reconstructed = hsss.reconstruct(&hierarchical_shares[0..1]).unwrap();
assert_eq!(reconstructed, secret);
}
#[test]
fn test_hsss_integration_example() {
let mut hsss = Hsss::builder(5)
.add_level("President", 5) .add_level("VP", 3) .add_level("Executive", 2) .build()
.unwrap();
let secret = b"Top secret company information";
let hierarchical_shares = hsss.split_secret(secret).unwrap();
assert_eq!(hierarchical_shares.len(), 3);
assert_eq!(hierarchical_shares[0].level_name, "President");
assert_eq!(hierarchical_shares[0].shares.len(), 5);
assert_eq!(hierarchical_shares[1].level_name, "VP");
assert_eq!(hierarchical_shares[1].shares.len(), 3);
assert_eq!(hierarchical_shares[2].level_name, "Executive");
assert_eq!(hierarchical_shares[2].shares.len(), 2);
let reconstructed = hsss.reconstruct(&hierarchical_shares[0..1]).unwrap();
assert_eq!(reconstructed, secret);
let reconstructed = hsss.reconstruct(&hierarchical_shares[1..3]).unwrap();
assert_eq!(reconstructed, secret);
let result = hsss.reconstruct(&hierarchical_shares[1..2]);
assert!(matches!(result, Err(ShamirError::InsufficientShares { needed: 5, got: 3 })));
let result = hsss.reconstruct(&hierarchical_shares[2..3]);
assert!(matches!(result, Err(ShamirError::InsufficientShares { needed: 5, got: 2 })));
let reconstructed = hsss.reconstruct(&hierarchical_shares).unwrap();
assert_eq!(reconstructed, secret);
}
}