Skip to content

Commit a67224f

Browse files
committed
test: add test case for shamir algorithm
1 parent 6c76f47 commit a67224f

2 files changed

Lines changed: 297 additions & 3 deletions

File tree

src/http/sys.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ async fn sys_init_put_request_handler(
103103

104104
let result = core.init(&seal_config)?;
105105

106-
let resp =
107-
InitResponse { keys: result.secret_shares.iter().map(hex::encode).collect(), root_token: result.root_token.clone() };
106+
let resp = InitResponse {
107+
keys: result.secret_shares.iter().map(hex::encode).collect(),
108+
root_token: result.root_token.clone(),
109+
};
108110

109111
Ok(response_json_ok(None, resp))
110112
}

src/shamir.rs

Lines changed: 293 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl ShamirSecret {
161161
}
162162

163163
pub fn split(secret: &[u8], part: u8, threshold: u8) -> Result<Zeroizing<Vec<Vec<u8>>>, RvError> {
164-
if part < threshold || threshold < 2 {
164+
if part < threshold || threshold < 2 || part >=255 {
165165
return Err(RvError::ErrShamirShareCountInvalid);
166166
}
167167

@@ -390,4 +390,296 @@ mod tests {
390390
let secret = recovered.unwrap();
391391
assert_ne!(&secret, "Hello World!".as_bytes());
392392
}
393+
394+
#[test]
395+
fn test_split_basic_functionality() {
396+
let secret = b"test secret data";
397+
let threshold = 3;
398+
let total_shares = 5;
399+
400+
let result = ShamirSecret::split(secret, total_shares, threshold);
401+
assert!(result.is_ok());
402+
403+
let shares = result.unwrap();
404+
println!("shares: {:?}", shares);
405+
assert_eq!(shares.len(), total_shares as usize);
406+
407+
let expected_length = secret.len() + 1; // secret length + 1 byte for share ID
408+
for share in shares.iter() {
409+
assert_eq!(share.len(), expected_length);
410+
}
411+
412+
for (i, share) in shares.iter().enumerate() {
413+
assert_eq!(share[share.len() - 1], (i + 1) as u8);
414+
}
415+
}
416+
417+
#[test]
418+
fn test_split_parameter_validation() {
419+
let secret = b"test secret";
420+
421+
// threshold < 2 should fail
422+
let result = ShamirSecret::split(secret, 3, 1);
423+
assert!(result.is_err());
424+
425+
// part < threshold should fail
426+
let result = ShamirSecret::split(secret, 2, 3);
427+
assert!(result.is_err());
428+
429+
// Valid parameters should succeed
430+
let result = ShamirSecret::split(secret, 3, 2);
431+
assert!(result.is_ok());
432+
433+
let result = ShamirSecret::split(secret, 5, 3);
434+
assert!(result.is_ok());
435+
}
436+
437+
#[test]
438+
fn test_split_minimum_valid_parameters() {
439+
let secret = b"minimum test";
440+
let threshold = 2;
441+
let total_shares = 2;
442+
443+
let result = ShamirSecret::split(secret, total_shares, threshold);
444+
assert!(result.is_ok());
445+
446+
let shares = result.unwrap();
447+
assert_eq!(shares.len(), 2);
448+
449+
// Test that we can recover the secret with both shares
450+
let recovered = ShamirSecret::combine(shares.to_vec());
451+
assert!(recovered.is_some());
452+
assert_eq!(recovered.unwrap(), secret);
453+
}
454+
455+
#[test]
456+
fn test_split_maximum_shares() {
457+
let secret = b"max shares test";
458+
let threshold = 2;
459+
let total_shares = 255; // Maximum possible share count
460+
461+
let result = ShamirSecret::split(secret, total_shares, threshold);
462+
assert!(result.is_err());
463+
464+
let result = ShamirSecret::split(secret, total_shares - 1, threshold);
465+
assert!(result.is_ok());
466+
467+
let shares = result.unwrap();
468+
assert_eq!(shares.len(), (total_shares - 1) as usize);
469+
470+
// Test recovery with minimum threshold
471+
let minimal_shares = vec![shares[0].clone(), shares[1].clone()];
472+
let recovered = ShamirSecret::combine(minimal_shares);
473+
assert!(recovered.is_some());
474+
assert_eq!(recovered.unwrap(), secret);
475+
}
476+
477+
#[test]
478+
fn test_split_empty_secret() {
479+
let secret = b"";
480+
let threshold = 2;
481+
let total_shares = 3;
482+
483+
let result = ShamirSecret::split(secret, total_shares, threshold);
484+
assert!(result.is_ok());
485+
486+
let shares = result.unwrap();
487+
assert_eq!(shares.len(), 3);
488+
489+
// Each share should only contain the share ID
490+
for share in shares.iter() {
491+
assert_eq!(share.len(), 1); // Only share ID
492+
}
493+
494+
// Recovery should work
495+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone()]);
496+
assert!(recovered.is_some());
497+
assert_eq!(recovered.unwrap(), secret);
498+
}
499+
500+
#[test]
501+
fn test_split_single_byte_secret() {
502+
let secret = b"A";
503+
let threshold = 3;
504+
let total_shares = 5;
505+
506+
let result = ShamirSecret::split(secret, total_shares, threshold);
507+
assert!(result.is_ok());
508+
509+
let shares = result.unwrap();
510+
assert_eq!(shares.len(), 5);
511+
512+
for share in shares.iter() {
513+
assert_eq!(share.len(), 2); // 1 byte secret + 1 byte share ID
514+
}
515+
516+
// Recovery with exact threshold
517+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone(), shares[2].clone()]);
518+
assert!(recovered.is_some());
519+
assert_eq!(recovered.unwrap(), secret);
520+
}
521+
522+
#[test]
523+
fn test_split_large_secret() {
524+
let secret = vec![0xAB; 1000]; // 1KB of data
525+
let threshold = 4;
526+
let total_shares = 7;
527+
528+
let result = ShamirSecret::split(&secret, total_shares, threshold);
529+
assert!(result.is_ok());
530+
531+
let shares = result.unwrap();
532+
assert_eq!(shares.len(), 7);
533+
534+
for share in shares.iter() {
535+
assert_eq!(share.len(), 1001); // 1000 bytes secret + 1 byte share ID
536+
}
537+
538+
// Recovery with more than threshold
539+
let recovered = ShamirSecret::combine(vec![
540+
shares[0].clone(),
541+
shares[1].clone(),
542+
shares[2].clone(),
543+
shares[3].clone(),
544+
shares[4].clone(),
545+
]);
546+
assert!(recovered.is_some());
547+
assert_eq!(recovered.unwrap(), secret);
548+
}
549+
550+
#[test]
551+
fn test_split_recovery_with_exact_threshold() {
552+
let secret = b"exact threshold test";
553+
let threshold = 4;
554+
let total_shares = 6;
555+
556+
let shares = ShamirSecret::split(secret, total_shares, threshold).unwrap();
557+
558+
// Test recovery with exactly threshold shares
559+
let recovered =
560+
ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone(), shares[2].clone(), shares[3].clone()]);
561+
assert!(recovered.is_some());
562+
assert_eq!(recovered.unwrap(), secret);
563+
}
564+
565+
#[test]
566+
fn test_split_recovery_with_insufficient_shares() {
567+
let secret = b"insufficient shares test";
568+
let threshold = 4;
569+
let total_shares = 6;
570+
571+
let shares = ShamirSecret::split(secret, total_shares, threshold).unwrap();
572+
573+
// Test recovery with less than threshold shares (should fail or give wrong result)
574+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone(), shares[2].clone()]);
575+
576+
// With insufficient shares, either recovery fails or gives wrong result
577+
if let Some(recovered_secret) = recovered {
578+
assert_ne!(recovered_secret, secret);
579+
}
580+
}
581+
582+
#[test]
583+
fn test_split_recovery_with_different_share_combinations() {
584+
let secret = b"combination test";
585+
let threshold = 3;
586+
let total_shares = 5;
587+
588+
let shares = ShamirSecret::split(secret, total_shares, threshold).unwrap();
589+
590+
// Test different combinations of shares
591+
let combinations = vec![
592+
vec![0, 1, 2],
593+
vec![0, 2, 4],
594+
vec![1, 3, 4],
595+
vec![2, 3, 4],
596+
vec![0, 1, 3, 4], // More than threshold
597+
];
598+
599+
for combination in combinations {
600+
let selected_shares: Vec<_> = combination.iter().map(|&i| shares[i].clone()).collect();
601+
let recovered = ShamirSecret::combine(selected_shares);
602+
assert!(recovered.is_some());
603+
assert_eq!(recovered.unwrap(), secret);
604+
}
605+
}
606+
607+
#[test]
608+
fn test_split_deterministic_behavior() {
609+
// Note: ShamirSecret uses random coefficients, so splits are not deterministic
610+
// But we can test that the same secret with same parameters produces valid shares
611+
let secret = b"deterministic test";
612+
let threshold = 3;
613+
let total_shares = 5;
614+
615+
let shares1 = ShamirSecret::split(secret, total_shares, threshold).unwrap();
616+
let shares2 = ShamirSecret::split(secret, total_shares, threshold).unwrap();
617+
618+
// Shares should be different due to randomness
619+
assert_ne!(shares1[0], shares2[0]);
620+
621+
// But both should recover to the same secret
622+
let recovered1 = ShamirSecret::combine(vec![shares1[0].clone(), shares1[1].clone(), shares1[2].clone()]);
623+
let recovered2 = ShamirSecret::combine(vec![shares2[0].clone(), shares2[1].clone(), shares2[2].clone()]);
624+
625+
assert!(recovered1.is_some());
626+
assert!(recovered2.is_some());
627+
assert_eq!(recovered1.unwrap(), secret);
628+
assert_eq!(recovered2.unwrap(), secret);
629+
}
630+
631+
#[test]
632+
fn test_split_binary_data() {
633+
// Test with binary data including null bytes and high values
634+
let secret = vec![0x00, 0xFF, 0x80, 0x7F, 0x01, 0xFE, 0x55, 0xAA];
635+
let threshold = 3;
636+
let total_shares = 5;
637+
638+
let shares = ShamirSecret::split(&secret, total_shares, threshold).unwrap();
639+
640+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone(), shares[2].clone()]);
641+
assert!(recovered.is_some());
642+
assert_eq!(recovered.unwrap(), secret);
643+
}
644+
645+
#[test]
646+
fn test_split_edge_case_thresholds() {
647+
let secret = b"edge case test";
648+
649+
// Test with threshold = total_shares
650+
let shares = ShamirSecret::split(secret, 3, 3).unwrap();
651+
assert_eq!(shares.len(), 3);
652+
653+
// Need all shares to recover
654+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone(), shares[2].clone()]);
655+
assert!(recovered.is_some());
656+
assert_eq!(recovered.unwrap(), secret);
657+
658+
// With one less share, recovery should fail or give wrong result
659+
let recovered = ShamirSecret::combine(vec![shares[0].clone(), shares[1].clone()]);
660+
if let Some(recovered_secret) = recovered {
661+
assert_ne!(recovered_secret, secret);
662+
}
663+
}
664+
665+
#[test]
666+
fn test_split_share_uniqueness() {
667+
let secret = b"uniqueness test";
668+
let threshold = 3;
669+
let total_shares = 5;
670+
671+
let shares = ShamirSecret::split(secret, total_shares, threshold).unwrap();
672+
673+
// All shares should be unique
674+
for i in 0..shares.len() {
675+
for j in (i + 1)..shares.len() {
676+
assert_ne!(shares[i], shares[j]);
677+
}
678+
}
679+
680+
// Share IDs should be unique and in order
681+
for (i, share) in shares.iter().enumerate() {
682+
assert_eq!(share[share.len() - 1], (i + 1) as u8);
683+
}
684+
}
393685
}

0 commit comments

Comments
 (0)