1use crate::error::{Error, Result};
39use crate::primitives::bsv::polynomial::{PointInFiniteField, Polynomial};
40use crate::primitives::encoding::{from_base58, to_base58};
41use crate::primitives::hash::sha256;
42use crate::primitives::BigNumber;
43use crate::primitives::PrivateKey;
44
45#[derive(Clone, Debug)]
50pub struct KeyShares {
51 pub points: Vec<PointInFiniteField>,
53 pub threshold: usize,
55 pub integrity: String,
57}
58
59impl KeyShares {
60 pub fn new(points: Vec<PointInFiniteField>, threshold: usize, integrity: String) -> Self {
68 Self {
69 points,
70 threshold,
71 integrity,
72 }
73 }
74
75 pub fn from_backup_format(shares: &[String]) -> Result<Self> {
108 if shares.is_empty() {
109 return Err(Error::CryptoError(
110 "No shares provided for recovery".to_string(),
111 ));
112 }
113
114 let mut points = Vec::with_capacity(shares.len());
115 let mut threshold: Option<usize> = None;
116 let mut integrity: Option<String> = None;
117
118 for (idx, share) in shares.iter().enumerate() {
119 let (point, t, i) = decode_share(share)?;
120
121 if let Some(existing_threshold) = threshold {
123 if existing_threshold != t {
124 return Err(Error::CryptoError(format!(
125 "Threshold mismatch: share 0 has threshold {}, share {} has threshold {}",
126 existing_threshold, idx, t
127 )));
128 }
129 } else {
130 threshold = Some(t);
131 }
132
133 if let Some(ref existing_integrity) = integrity {
134 if existing_integrity != &i {
135 return Err(Error::CryptoError(format!(
136 "Integrity mismatch: share 0 has integrity '{}', share {} has integrity '{}'",
137 existing_integrity, idx, i
138 )));
139 }
140 } else {
141 integrity = Some(i);
142 }
143
144 points.push(point);
145 }
146
147 Ok(Self {
148 points,
149 threshold: threshold.unwrap(),
150 integrity: integrity.unwrap(),
151 })
152 }
153
154 pub fn to_backup_format(&self) -> Vec<String> {
178 self.points
179 .iter()
180 .map(|point| {
181 format!(
182 "{}.{}.{}",
183 point.to_point_string(),
184 self.threshold,
185 self.integrity
186 )
187 })
188 .collect()
189 }
190
191 pub fn recover_private_key(&self) -> Result<PrivateKey> {
217 if self.points.len() < self.threshold {
218 return Err(Error::CryptoError(format!(
219 "Insufficient shares: have {}, need {}",
220 self.points.len(),
221 self.threshold
222 )));
223 }
224
225 let poly = Polynomial::new(self.points.clone(), self.threshold);
227 let secret = poly.value_at(&BigNumber::zero());
228
229 let secret_bytes = secret.to_bytes_be(32);
232
233 let key = PrivateKey::from_bytes(&secret_bytes)?;
235
236 let computed_integrity = compute_integrity(&key);
238 if computed_integrity != self.integrity {
239 return Err(Error::CryptoError(format!(
240 "Integrity check failed: computed '{}', expected '{}'",
241 computed_integrity, self.integrity
242 )));
243 }
244
245 Ok(key)
246 }
247}
248
249pub fn split_private_key(key: &PrivateKey, threshold: usize, total: usize) -> Result<KeyShares> {
286 if threshold < 2 {
288 return Err(Error::CryptoError(
289 "Threshold must be at least 2".to_string(),
290 ));
291 }
292 if total < threshold {
293 return Err(Error::CryptoError(format!(
294 "Total shares ({}) must be at least threshold ({})",
295 total, threshold
296 )));
297 }
298 if threshold > 255 {
299 return Err(Error::CryptoError(
300 "Threshold cannot exceed 255".to_string(),
301 ));
302 }
303
304 let p = BigNumber::secp256k1_prime();
305
306 let secret = BigNumber::from_bytes_be(&key.to_bytes());
308
309 let mut coefficients = Vec::with_capacity(threshold);
312 coefficients.push(secret);
313
314 for _ in 1..threshold {
315 let random_key = PrivateKey::random();
317 let coeff = BigNumber::from_bytes_be(&random_key.to_bytes()).modulo(&p);
318 coefficients.push(coeff);
319 }
320
321 let mut points = Vec::with_capacity(total);
323
324 for i in 1..=total {
325 let x = BigNumber::from_u64(i as u64);
326 let y = evaluate_polynomial(&coefficients, &x, &p);
327 points.push(PointInFiniteField::new(x, y));
328 }
329
330 let integrity = compute_integrity(key);
332
333 Ok(KeyShares {
334 points,
335 threshold,
336 integrity,
337 })
338}
339
340fn evaluate_polynomial(
347 coefficients: &[BigNumber],
348 x: &BigNumber,
349 modulus: &BigNumber,
350) -> BigNumber {
351 let mut result = BigNumber::zero();
353
354 for coeff in coefficients.iter().rev() {
355 result = result.mul(x).add(coeff).modulo(modulus);
356 }
357
358 result
359}
360
361fn compute_integrity(key: &PrivateKey) -> String {
365 let hash = sha256(&key.to_bytes());
366 let b58 = to_base58(&hash);
367
368 if b58.len() >= 4 {
370 b58[..4].to_string()
371 } else {
372 b58
373 }
374}
375
376fn decode_share(share: &str) -> Result<(PointInFiniteField, usize, String)> {
382 let components: Vec<&str> = share.split('.').collect();
383
384 if components.len() != 4 {
385 return Err(Error::CryptoError(format!(
386 "Invalid share format: expected 'base58(x).base58(y).threshold.integrity', got '{}'",
387 share
388 )));
389 }
390
391 let x_bytes = from_base58(components[0])?;
393 let y_bytes = from_base58(components[1])?;
394
395 let x = BigNumber::from_bytes_be(&x_bytes);
396 let y = BigNumber::from_bytes_be(&y_bytes);
397
398 let point = PointInFiniteField::new(x, y);
399
400 let threshold: usize = components[2].parse().map_err(|e| {
402 Error::CryptoError(format!(
403 "Invalid threshold in share: {} ({})",
404 components[2], e
405 ))
406 })?;
407
408 let integrity = components[3].to_string();
410
411 Ok((point, threshold, integrity))
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_split_recover_roundtrip() {
420 let key = PrivateKey::random();
421 let shares = split_private_key(&key, 3, 5).unwrap();
422
423 assert_eq!(shares.points.len(), 5);
424 assert_eq!(shares.threshold, 3);
425
426 let subset = KeyShares::new(shares.points[0..3].to_vec(), 3, shares.integrity.clone());
428 let recovered = subset.recover_private_key().unwrap();
429 assert_eq!(key.to_bytes(), recovered.to_bytes());
430 }
431
432 #[test]
433 fn test_split_recover_different_subsets() {
434 let key = PrivateKey::random();
435 let shares = split_private_key(&key, 3, 5).unwrap();
436
437 let subsets = vec![
439 vec![0, 1, 2],
440 vec![0, 1, 3],
441 vec![0, 1, 4],
442 vec![0, 2, 3],
443 vec![0, 2, 4],
444 vec![0, 3, 4],
445 vec![1, 2, 3],
446 vec![1, 2, 4],
447 vec![1, 3, 4],
448 vec![2, 3, 4],
449 ];
450
451 for indices in subsets {
452 let points: Vec<_> = indices.iter().map(|&i| shares.points[i].clone()).collect();
453 let subset = KeyShares::new(points, 3, shares.integrity.clone());
454 let recovered = subset.recover_private_key().unwrap();
455 assert_eq!(
456 key.to_bytes(),
457 recovered.to_bytes(),
458 "Failed for indices {:?}",
459 indices
460 );
461 }
462 }
463
464 #[test]
465 fn test_backup_format_roundtrip() {
466 let key = PrivateKey::random();
467 let shares = split_private_key(&key, 3, 5).unwrap();
468
469 let backup = shares.to_backup_format();
471 assert_eq!(backup.len(), 5);
472
473 for s in &backup {
475 assert_eq!(s.split('.').count(), 4);
476 }
477
478 let restored = KeyShares::from_backup_format(&backup[1..4]).unwrap();
480 let recovered = restored.recover_private_key().unwrap();
481 assert_eq!(key.to_bytes(), recovered.to_bytes());
482 }
483
484 #[test]
485 fn test_minimum_threshold() {
486 let key = PrivateKey::random();
488 let shares = split_private_key(&key, 2, 3).unwrap();
489
490 let subset = KeyShares::new(shares.points[0..2].to_vec(), 2, shares.integrity.clone());
491 let recovered = subset.recover_private_key().unwrap();
492 assert_eq!(key.to_bytes(), recovered.to_bytes());
493 }
494
495 #[test]
496 fn test_exact_threshold_equals_total() {
497 let key = PrivateKey::random();
499 let shares = split_private_key(&key, 5, 5).unwrap();
500
501 let recovered = shares.recover_private_key().unwrap();
502 assert_eq!(key.to_bytes(), recovered.to_bytes());
503 }
504
505 #[test]
506 fn test_insufficient_shares() {
507 let key = PrivateKey::random();
508 let shares = split_private_key(&key, 3, 5).unwrap();
509
510 let subset = KeyShares::new(shares.points[0..2].to_vec(), 3, shares.integrity.clone());
512 let result = subset.recover_private_key();
513 assert!(matches!(result, Err(Error::CryptoError(_))));
514 }
515
516 #[test]
517 fn test_invalid_threshold() {
518 let key = PrivateKey::random();
519
520 assert!(matches!(
522 split_private_key(&key, 1, 5),
523 Err(Error::CryptoError(_))
524 ));
525 assert!(matches!(
526 split_private_key(&key, 0, 5),
527 Err(Error::CryptoError(_))
528 ));
529
530 assert!(matches!(
532 split_private_key(&key, 5, 3),
533 Err(Error::CryptoError(_))
534 ));
535 }
536
537 #[test]
538 fn test_integrity_check_fails_on_corruption() {
539 let key = PrivateKey::random();
540 let shares = split_private_key(&key, 2, 3).unwrap();
541
542 let corrupted = KeyShares::new(
544 shares.points.clone(),
545 2,
546 "XXXX".to_string(), );
548
549 let result = corrupted.recover_private_key();
550 assert!(matches!(result, Err(Error::CryptoError(_))));
551 assert!(result
552 .unwrap_err()
553 .to_string()
554 .contains("Integrity check failed"));
555 }
556
557 #[test]
558 fn test_backup_format_parsing() {
559 let key = PrivateKey::random();
560 let shares = split_private_key(&key, 2, 3).unwrap();
561 let backup = shares.to_backup_format();
562
563 let parsed = KeyShares::from_backup_format(&backup).unwrap();
565 assert_eq!(parsed.threshold, shares.threshold);
566 assert_eq!(parsed.integrity, shares.integrity);
567 assert_eq!(parsed.points.len(), shares.points.len());
568 }
569
570 #[test]
571 fn test_mismatched_threshold_in_shares() {
572 let share1 = "2.abc.3.XXXX".to_string();
574 let share2 = "3.def.4.XXXX".to_string(); let result = KeyShares::from_backup_format(&[share1, share2]);
577 assert!(matches!(result, Err(Error::CryptoError(_))));
578 assert!(result
579 .unwrap_err()
580 .to_string()
581 .contains("Threshold mismatch"));
582 }
583
584 #[test]
585 fn test_mismatched_integrity_in_shares() {
586 let share1 = "2.abc.3.AAAA".to_string();
588 let share2 = "3.def.3.BBBB".to_string(); let result = KeyShares::from_backup_format(&[share1, share2]);
591 assert!(matches!(result, Err(Error::CryptoError(_))));
592 assert!(result
593 .unwrap_err()
594 .to_string()
595 .contains("Integrity mismatch"));
596 }
597
598 #[test]
599 fn test_known_private_key() {
600 let key = PrivateKey::from_hex(
602 "0000000000000000000000000000000000000000000000000000000000000001",
603 )
604 .unwrap();
605
606 let shares = split_private_key(&key, 2, 3).unwrap();
607 let recovered = shares.recover_private_key().unwrap();
608
609 assert_eq!(key.to_bytes(), recovered.to_bytes());
610 }
611
612 #[test]
613 fn test_large_number_of_shares() {
614 let key = PrivateKey::random();
615 let shares = split_private_key(&key, 5, 10).unwrap();
616
617 assert_eq!(shares.points.len(), 10);
618
619 let subset = KeyShares::new(
621 shares.points[5..10].to_vec(), 5,
623 shares.integrity.clone(),
624 );
625 let recovered = subset.recover_private_key().unwrap();
626 assert_eq!(key.to_bytes(), recovered.to_bytes());
627 }
628
629 #[test]
630 fn test_compute_integrity() {
631 let key = PrivateKey::from_hex(
632 "0000000000000000000000000000000000000000000000000000000000000001",
633 )
634 .unwrap();
635
636 let integrity = compute_integrity(&key);
637 assert_eq!(integrity.len(), 4);
639
640 let integrity2 = compute_integrity(&key);
642 assert_eq!(integrity, integrity2);
643 }
644
645 #[test]
646 fn test_evaluate_polynomial() {
647 let p = BigNumber::secp256k1_prime();
648
649 let coefficients = vec![
652 BigNumber::from_u64(5),
653 BigNumber::from_u64(3),
654 BigNumber::from_u64(2),
655 ];
656
657 assert_eq!(
658 evaluate_polynomial(&coefficients, &BigNumber::zero(), &p),
659 BigNumber::from_u64(5)
660 );
661 assert_eq!(
662 evaluate_polynomial(&coefficients, &BigNumber::from_u64(1), &p),
663 BigNumber::from_u64(10)
664 );
665 assert_eq!(
666 evaluate_polynomial(&coefficients, &BigNumber::from_u64(2), &p),
667 BigNumber::from_u64(19)
668 );
669 }
670
671 #[test]
672 fn test_decode_share() {
673 let key = PrivateKey::random();
675 let shares = split_private_key(&key, 2, 3).unwrap();
676 let backup = shares.to_backup_format();
677
678 let (point, threshold, integrity) = decode_share(&backup[0]).unwrap();
679 assert_eq!(threshold, 2);
680 assert_eq!(integrity, shares.integrity);
681 assert_eq!(point.x, shares.points[0].x);
682 assert_eq!(point.y, shares.points[0].y);
683 }
684
685 #[test]
686 fn test_decode_share_invalid_format() {
687 assert!(matches!(decode_share("a.b.c"), Err(Error::CryptoError(_))));
689
690 assert!(matches!(
692 decode_share("a.b.c.d.e"),
693 Err(Error::CryptoError(_))
694 ));
695
696 assert!(matches!(
698 decode_share("2.abc.notanumber.XXXX"),
699 Err(Error::CryptoError(_))
700 ));
701 }
702
703 #[test]
704 fn test_empty_shares() {
705 let result = KeyShares::from_backup_format(&[]);
706 assert!(matches!(result, Err(Error::CryptoError(_))));
707 }
708
709 #[test]
714 fn test_threshold_greater_than_total_shares() {
715 let key = PrivateKey::random();
717 let result = split_private_key(&key, 50, 5);
718 assert!(matches!(result, Err(Error::CryptoError(_))));
719 assert!(
720 result.unwrap_err().to_string().contains("must be at least"),
721 "Expected error about total shares being less than threshold"
722 );
723 }
724
725 #[test]
726 fn test_total_shares_less_than_2() {
727 let key = PrivateKey::random();
729
730 let result = split_private_key(&key, 2, 1);
732 assert!(matches!(result, Err(Error::CryptoError(_))));
733
734 let result = split_private_key(&key, 1, 1);
736 assert!(matches!(result, Err(Error::CryptoError(_))));
737 }
738
739 #[test]
740 fn test_duplicate_shares_in_recovery() {
741 let key = PrivateKey::random();
745 let shares = split_private_key(&key, 3, 5).unwrap();
746 let backup = shares.to_backup_format();
747
748 let recovery = KeyShares::from_backup_format(&[
750 backup[0].clone(),
751 backup[1].clone(),
752 backup[1].clone(),
753 ])
754 .unwrap();
755
756 let result = recovery.recover_private_key();
759 assert!(
760 matches!(result, Err(Error::CryptoError(_))),
761 "Expected CryptoError when using duplicate shares for recovery, got {:?}",
762 result
763 );
764 }
765
766 #[test]
767 fn test_fewer_points_than_threshold() {
768 let key = PrivateKey::random();
771 let shares = split_private_key(&key, 3, 5).unwrap();
772
773 let subset = KeyShares::new(shares.points[..2].to_vec(), 3, shares.integrity.clone());
775 let result = subset.recover_private_key();
776 assert!(matches!(result, Err(Error::CryptoError(_))));
777 assert!(
778 result
779 .unwrap_err()
780 .to_string()
781 .contains("Insufficient shares"),
782 "Expected 'Insufficient shares' error message"
783 );
784 }
785
786 #[test]
787 fn test_consistency_across_multiple_splits() {
788 let key = PrivateKey::random();
792
793 let shares1 = split_private_key(&key, 3, 5).unwrap();
794 let shares2 = split_private_key(&key, 3, 5).unwrap();
795
796 assert_ne!(
798 shares1.points[0].y, shares2.points[0].y,
799 "Two splits of the same key should produce different shares due to randomness"
800 );
801
802 let recovered1 = KeyShares::new(shares1.points[..3].to_vec(), 3, shares1.integrity.clone())
804 .recover_private_key()
805 .unwrap();
806 let recovered2 = KeyShares::new(shares2.points[..3].to_vec(), 3, shares2.integrity.clone())
807 .recover_private_key()
808 .unwrap();
809
810 assert_eq!(key.to_bytes(), recovered1.to_bytes());
811 assert_eq!(key.to_bytes(), recovered2.to_bytes());
812
813 assert_eq!(shares1.integrity, shares2.integrity);
815 }
816
817 #[test]
818 fn test_different_recovery_subsets() {
819 let key = PrivateKey::random();
822 let shares = split_private_key(&key, 3, 5).unwrap();
823
824 let all_subsets: Vec<Vec<usize>> = vec![
825 vec![0, 1, 2],
826 vec![0, 1, 3],
827 vec![0, 1, 4],
828 vec![0, 2, 3],
829 vec![0, 2, 4],
830 vec![0, 3, 4],
831 vec![1, 2, 3],
832 vec![1, 2, 4],
833 vec![1, 3, 4],
834 vec![2, 3, 4],
835 ];
836
837 for subset_indices in &all_subsets {
838 let subset_points: Vec<_> = subset_indices
839 .iter()
840 .map(|&i| shares.points[i].clone())
841 .collect();
842 let subset = KeyShares::new(subset_points, 3, shares.integrity.clone());
843 let recovered = subset.recover_private_key().unwrap();
844 assert_eq!(
845 key.to_bytes(),
846 recovered.to_bytes(),
847 "Recovery failed for subset {:?}",
848 subset_indices
849 );
850 }
851 }
852
853 #[test]
854 fn test_single_share_threshold() {
855 let key = PrivateKey::random();
857 let result = split_private_key(&key, 1, 5);
858 assert!(matches!(result, Err(Error::CryptoError(_))));
859 assert!(
860 result.unwrap_err().to_string().contains("at least 2"),
861 "Expected error about threshold being at least 2"
862 );
863 }
864
865 #[test]
866 fn test_max_shares() {
867 let key = PrivateKey::random();
869 let shares = split_private_key(&key, 3, 255).unwrap();
870 assert_eq!(shares.points.len(), 255);
871
872 let subset = KeyShares::new(shares.points[..3].to_vec(), 3, shares.integrity.clone());
874 let recovered = subset.recover_private_key().unwrap();
875 assert_eq!(key.to_bytes(), recovered.to_bytes());
876
877 let subset = KeyShares::new(
879 shares.points[252..255].to_vec(),
880 3,
881 shares.integrity.clone(),
882 );
883 let recovered = subset.recover_private_key().unwrap();
884 assert_eq!(key.to_bytes(), recovered.to_bytes());
885
886 let subset = KeyShares::new(
888 vec![
889 shares.points[0].clone(),
890 shares.points[127].clone(),
891 shares.points[254].clone(),
892 ],
893 3,
894 shares.integrity.clone(),
895 );
896 let recovered = subset.recover_private_key().unwrap();
897 assert_eq!(key.to_bytes(), recovered.to_bytes());
898 }
899
900 #[test]
901 fn test_threshold_exceeds_255() {
902 let key = PrivateKey::random();
904 let result = split_private_key(&key, 256, 300);
905 assert!(matches!(result, Err(Error::CryptoError(_))));
906 assert!(
907 result.unwrap_err().to_string().contains("255"),
908 "Expected error about threshold exceeding 255"
909 );
910 }
911
912 #[test]
913 fn test_different_thresholds_and_shares() {
914 let test_cases = vec![(2, 3), (2, 5), (3, 5), (4, 7), (5, 10), (10, 10)];
917
918 for (threshold, total) in test_cases {
919 let key = PrivateKey::random();
920 let shares = split_private_key(&key, threshold, total).unwrap();
921 assert_eq!(shares.points.len(), total);
922
923 let subset = KeyShares::new(
924 shares.points[..threshold].to_vec(),
925 threshold,
926 shares.integrity.clone(),
927 );
928 let recovered = subset.recover_private_key().unwrap();
929 assert_eq!(
930 key.to_bytes(),
931 recovered.to_bytes(),
932 "Failed for threshold={}, total={}",
933 threshold,
934 total
935 );
936 }
937 }
938
939 #[test]
940 fn test_recovery_with_more_shares_than_threshold() {
941 let key = PrivateKey::random();
943 let shares = split_private_key(&key, 3, 5).unwrap();
944
945 let recovered = shares.recover_private_key().unwrap();
947 assert_eq!(key.to_bytes(), recovered.to_bytes());
948
949 let subset = KeyShares::new(shares.points[..4].to_vec(), 3, shares.integrity.clone());
951 let recovered = subset.recover_private_key().unwrap();
952 assert_eq!(key.to_bytes(), recovered.to_bytes());
953 }
954
955 #[test]
956 fn test_recovery_with_wrong_shares_fails_integrity() {
957 let key1 = PrivateKey::random();
959 let key2 = PrivateKey::random();
960
961 let shares1 = split_private_key(&key1, 2, 3).unwrap();
962 let shares2 = split_private_key(&key2, 2, 3).unwrap();
963
964 let mixed = KeyShares::new(
966 vec![shares1.points[0].clone(), shares2.points[1].clone()],
967 2,
968 shares1.integrity.clone(),
969 );
970 let result = mixed.recover_private_key();
971 assert!(
973 matches!(result, Err(Error::CryptoError(_))),
974 "Expected CryptoError when mixing shares from different keys, got {:?}",
975 result
976 );
977 }
978
979 #[test]
980 fn test_multiple_recovery_iterations() {
981 for _ in 0..10 {
984 let key = PrivateKey::random();
985 let shares = split_private_key(&key, 3, 5).unwrap();
986 let subset = KeyShares::new(shares.points[..3].to_vec(), 3, shares.integrity.clone());
987 let recovered = subset.recover_private_key().unwrap();
988 assert_eq!(key.to_bytes(), recovered.to_bytes());
989 }
990 }
991
992 #[test]
993 fn test_backup_recovery_full_roundtrip() {
994 for _ in 0..3 {
996 let key = PrivateKey::random();
997 let shares = split_private_key(&key, 3, 5).unwrap();
998 let backup = shares.to_backup_format();
999 assert_eq!(backup.len(), 5);
1000
1001 let recovered_shares = KeyShares::from_backup_format(&backup[..3]).unwrap();
1003 let recovered_key = recovered_shares.recover_private_key().unwrap();
1004 assert_eq!(key.to_bytes(), recovered_key.to_bytes());
1005 }
1006 }
1007
1008 #[test]
1009 fn test_zero_threshold() {
1010 let key = PrivateKey::random();
1012 let result = split_private_key(&key, 0, 5);
1013 assert!(matches!(result, Err(Error::CryptoError(_))));
1014 }
1015
1016 #[test]
1017 fn test_zero_total_shares() {
1018 let key = PrivateKey::random();
1020 let result = split_private_key(&key, 2, 0);
1021 assert!(matches!(result, Err(Error::CryptoError(_))));
1022 }
1023}