dark_crystal_secret_sharing_rust/
lib.rs1use rand::{thread_rng, Rng};
9use sharks::{Share, Sharks};
10use std::collections::HashSet;
11use std::convert::{TryFrom, TryInto};
12use std::fmt;
13use xsalsa20poly1305::aead::generic_array::GenericArray;
14use zeroize::Zeroize;
15
16pub mod encrypt;
17
18fn get_shares_to_take(num_shares: u8) -> HashSet<u8> {
20 let mut rng = thread_rng();
21 let mut shares_to_take = HashSet::new();
22 while shares_to_take.len() < num_shares.into() {
23 let next_index = rng.gen::<u8>();
24 if next_index > 0 {
25 shares_to_take.insert(next_index);
26 }
27 }
28 shares_to_take
29}
30
31pub fn share(secret: &[u8], num_shares: u8, threshold: u8) -> Result<Vec<Vec<u8>>, ShareError> {
33 if num_shares < 2 {
34 return Err(ShareError {
35 message: "At least 2 shares required".to_string(),
36 });
37 }
38
39 if threshold < 2 {
40 return Err(ShareError {
41 message: "Threshold must be at least 2".to_string(),
42 });
43 }
44
45 if threshold > num_shares {
46 return Err(ShareError {
47 message: "Threshold must not be greater than the number of shares".to_string(),
48 });
49 }
50
51 let sharks = Sharks(threshold);
52 let dealer = sharks.dealer(&secret[..]);
53 let shares_to_take = get_shares_to_take(num_shares);
54
55 Ok(dealer
56 .take(255)
57 .map(|s| Vec::from(&s))
58 .filter(|s| shares_to_take.contains(&s[0]))
59 .collect())
60}
61
62pub fn combine(shares_bytes: Vec<Vec<u8>>) -> Result<Vec<u8>, RecoveryError> {
64 let shares: Vec<Share> = shares_bytes
65 .iter()
66 .map(|s| Share::try_from(s.as_slice()).unwrap())
67 .collect();
68
69 let sharks = Sharks(shares.len().try_into().unwrap());
70 match sharks.recover(&shares) {
71 Ok(val) => Ok(val),
72 Err(err) => Err(err.into()),
73 }
74}
75
76pub fn share_authenticated(
82 secret: &[u8],
83 num_shares: u8,
84 threshold: u8,
85) -> Result<(Vec<Vec<u8>>, Vec<u8>), ShareError> {
86 let mut key = encrypt::generate_key();
87 match share(&key, num_shares, threshold) {
88 Ok(shares) => {
89 let ciphertext = encrypt::encrypt(key, secret.to_vec()).unwrap();
90 key.zeroize();
91 Ok((shares, ciphertext))
92 }
93 Err(err) => Err(err),
94 }
95}
96
97pub fn combine_authenticated(
99 shares: Vec<Vec<u8>>,
100 ciphertext: Vec<u8>,
101) -> Result<Vec<u8>, RecoveryError> {
102 let recovered_key = combine(shares)?;
103 let key = GenericArray::from_slice(&recovered_key[..]);
104 match encrypt::decrypt(*key, ciphertext) {
105 Ok(val) => Ok(val),
106 Err(err) => Err(err.into()),
107 }
108}
109
110#[derive(Debug)]
112pub struct ShareError {
113 pub message: String,
114}
115
116impl fmt::Display for ShareError {
117 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118 write!(f, "Cannot create shares: {}", self.message)
119 }
120}
121
122#[derive(Debug)]
124pub struct RecoveryError {
125 pub message: String,
126}
127
128impl fmt::Display for RecoveryError {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 write!(f, "Error during recovery {}", self.message)
131 }
132}
133
134impl From<xsalsa20poly1305::aead::Error> for RecoveryError {
135 fn from(error: xsalsa20poly1305::aead::Error) -> Self {
136 RecoveryError {
137 message: error.to_string(),
138 }
139 }
140}
141
142impl From<&str> for RecoveryError {
143 fn from(error: &str) -> Self {
144 RecoveryError {
145 message: String::from(error),
146 }
147 }
148}
149
150pub fn default_threshold(number_of_shares: u8) -> u8 {
152 if number_of_shares == 2 {
153 return 2;
154 };
155 (number_of_shares as f32 * 0.75) as u8
156}
157
158pub fn thresold_sanity(threshold: u8, number_of_shares: u8) -> i32 {
163 (threshold as i32) - (default_threshold(number_of_shares) as i32)
164}
165
166#[cfg(test)]
167mod test {
168 use super::*;
169
170 #[test]
171 fn passes_on_all_shares() {
172 let original_secret = b"hello";
173 let shares = share(&original_secret[..], 5, 3).unwrap();
174 assert_eq!(shares.len(), 5);
175 let recovered_secret = combine(shares).unwrap();
176 assert_eq!(recovered_secret, b"hello");
177 }
178
179 #[test]
180 fn passes_on_threshold_amount() {
181 let original_secret = b"hello";
182 let mut shares = share(&original_secret[..], 5, 3).unwrap();
183 assert_eq!(shares.len(), 5);
184 shares.remove(0);
185 shares.remove(1);
186 assert_eq!(shares.len(), 3);
187 let recovered_secret = combine(shares).unwrap();
188 assert_eq!(recovered_secret, b"hello");
189 }
190
191 #[test]
192 fn authenticated() {
193 let original_secret = b"hello";
194 let (shares, ciphertext) = share_authenticated(&original_secret[..], 5, 3).unwrap();
195 assert_eq!(shares.len(), 5);
196 let recovered_secret = combine_authenticated(shares, ciphertext).unwrap();
197 assert_eq!(recovered_secret, b"hello");
198 }
199
200 #[test]
201 fn fails_on_insufficient_shares() {
202 let original_secret = b"hello";
203 let (mut shares, ciphertext) = share_authenticated(&original_secret[..], 5, 3).unwrap();
204 assert_eq!(shares.len(), 5);
205 shares.remove(0);
206 shares.remove(1);
207 shares.remove(2);
208 assert_eq!(shares.len(), 2);
209
210 let res = combine_authenticated(shares, ciphertext);
211 assert!(res.is_err());
212 }
213
214 #[test]
215 fn fails_on_impossible_threshold() {
216 let original_secret = b"hello";
217 let share_result = share(&original_secret[..], 5, 6);
218 assert!(share_result.is_err());
219 }
220
221 #[test]
222 fn fails_on_threshold_of_one() {
223 let original_secret = b"hello";
224 let share_result = share(&original_secret[..], 5, 1);
225 assert!(share_result.is_err());
226 }
227
228 #[test]
229 fn fails_on_creating_one_share() {
230 let original_secret = b"hello";
231 let share_result = share(&original_secret[..], 1, 1);
232 assert!(share_result.is_err());
233 }
234
235 #[test]
236 fn fails_on_bad_share() {
237 let original_secret = b"hello";
238 let (mut shares, ciphertext) = share_authenticated(&original_secret[..], 5, 3).unwrap();
239 shares[0] = b"bad share".to_vec();
240
241 let res = combine_authenticated(shares, ciphertext);
242 assert!(res.is_err());
243 }
244
245 #[test]
246 fn check_default_threshold() {
247 assert_eq!(thresold_sanity(7, 10), 0);
248 assert_eq!(thresold_sanity(5, 7), 0);
249 assert_eq!(thresold_sanity(3, 5), 0);
250 assert_eq!(thresold_sanity(2, 3), 0);
251 assert_eq!(thresold_sanity(2, 2), 0);
252
253 assert_eq!(thresold_sanity(2, 10), -5);
254 assert_eq!(thresold_sanity(9, 10), 2);
255 }
256}