qualia_client_core/
shamir_recovery.rs1use serde::{Deserialize, Serialize};
17
18fn gf_mul(mut a: u8, mut b: u8) -> u8 {
20 let mut p = 0u8;
21 for _ in 0..8 {
22 if b & 1 != 0 {
23 p ^= a;
24 }
25 let hi = a & 0x80;
26 a <<= 1;
27 if hi != 0 {
28 a ^= 0x1b; }
30 b >>= 1;
31 }
32 p
33}
34
35fn gf_inv(a: u8) -> u8 {
38 if a == 0 {
39 return 0;
40 }
41 let mut result = 1u8;
42 let mut base = a;
43 let mut exp = 254u32;
44 while exp > 0 {
45 if exp & 1 == 1 {
46 result = gf_mul(result, base);
47 }
48 base = gf_mul(base, base);
49 exp >>= 1;
50 }
51 result
52}
53
54fn gf_div(a: u8, b: u8) -> u8 {
55 gf_mul(a, gf_inv(b))
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct Share {
61 pub x: u8,
63 pub y: Vec<u8>,
65}
66
67fn random_byte() -> u8 {
69 rand::random::<u8>()
70}
71
72pub fn split(secret: &[u8], k: usize, n: usize) -> Result<Vec<Share>, String> {
75 if k == 0 || n == 0 {
76 return Err("k and n must be >= 1".into());
77 }
78 if k > n {
79 return Err(format!("threshold k={k} cannot exceed shares n={n}"));
80 }
81 if n > 255 {
82 return Err("n must be <= 255 (distinct nonzero abscissae in GF(2^8))".into());
83 }
84 let coeffs: Vec<Vec<u8>> = secret
86 .iter()
87 .map(|&s| {
88 let mut c = Vec::with_capacity(k);
89 c.push(s);
90 for _ in 1..k {
91 c.push(random_byte());
92 }
93 c
94 })
95 .collect();
96
97 let mut shares = Vec::with_capacity(n);
98 for x in 1..=(n as u8) {
99 let y: Vec<u8> = coeffs.iter().map(|c| eval_poly(c, x)).collect();
100 shares.push(Share { x, y });
101 }
102 Ok(shares)
103}
104
105fn eval_poly(coeffs: &[u8], x: u8) -> u8 {
107 let mut acc = 0u8;
108 for &c in coeffs.iter().rev() {
109 acc = gf_mul(acc, x) ^ c;
110 }
111 acc
112}
113
114pub fn reconstruct(shares: &[Share]) -> Result<Vec<u8>, String> {
118 if shares.is_empty() {
119 return Err("no shares".into());
120 }
121 let len = shares[0].y.len();
122 if shares.iter().any(|s| s.y.len() != len) {
123 return Err("shares have differing secret lengths".into());
124 }
125 for i in 0..shares.len() {
127 if shares[i].x == 0 {
128 return Err("share abscissa 0 is invalid".into());
129 }
130 for j in (i + 1)..shares.len() {
131 if shares[i].x == shares[j].x {
132 return Err("duplicate share abscissa".into());
133 }
134 }
135 }
136
137 let mut secret = vec![0u8; len];
138 for byte in 0..len {
139 let mut acc = 0u8;
140 for i in 0..shares.len() {
141 let xi = shares[i].x;
143 let mut num = 1u8;
144 let mut den = 1u8;
145 for j in 0..shares.len() {
146 if i == j {
147 continue;
148 }
149 let xj = shares[j].x;
150 num = gf_mul(num, xj);
151 den = gf_mul(den, xi ^ xj);
152 }
153 let l0 = gf_div(num, den);
154 acc ^= gf_mul(shares[i].y[byte], l0);
155 }
156 secret[byte] = acc;
157 }
158 Ok(secret)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn gf_field_axioms() {
167 for a in 1u8..=255 {
169 assert_eq!(gf_mul(a, 1), a);
170 assert_eq!(gf_mul(a, gf_inv(a)), 1, "a * a^-1 == 1 for a={a}");
171 }
172 assert_eq!(gf_mul(0, 5), 0);
173 assert_eq!(gf_mul(7, 9 ^ 13), gf_mul(7, 9) ^ gf_mul(7, 13));
175 }
176
177 #[test]
178 fn any_k_of_n_reconstructs_the_secret() {
179 let secret = b"a 32-byte data-encryption key!!!".to_vec();
180 let shares = split(&secret, 3, 5).unwrap();
181 assert_eq!(shares.len(), 5);
182 for subset in [[0usize, 1, 2], [0, 2, 4], [1, 3, 4], [2, 3, 4]] {
184 let chosen: Vec<Share> = subset.iter().map(|&i| shares[i].clone()).collect();
185 assert_eq!(
186 reconstruct(&chosen).unwrap(),
187 secret,
188 "subset {subset:?} recovers"
189 );
190 }
191 assert_eq!(reconstruct(&shares).unwrap(), secret);
193 }
194
195 #[test]
196 fn fewer_than_k_shares_do_not_recover_the_secret() {
197 let secret = b"top secret payload key".to_vec();
198 let shares = split(&secret, 3, 5).unwrap();
199 let two: Vec<Share> = vec![shares[0].clone(), shares[1].clone()];
201 assert_ne!(
202 reconstruct(&two).unwrap(),
203 secret,
204 "k-1 shares must not reveal the secret"
205 );
206 }
207
208 #[test]
209 fn threshold_equals_n_and_threshold_one() {
210 let secret = b"edge".to_vec();
211 let all = split(&secret, 4, 4).unwrap();
213 assert_eq!(reconstruct(&all).unwrap(), secret);
214 assert_ne!(reconstruct(&all[..3]).unwrap(), secret);
215 let ones = split(&secret, 1, 3).unwrap();
217 assert_eq!(reconstruct(&ones[1..2]).unwrap(), secret);
218 }
219
220 #[test]
221 fn bad_parameters_are_rejected() {
222 assert!(split(b"x", 0, 3).is_err());
223 assert!(split(b"x", 4, 3).is_err(), "k > n");
224 assert!(split(b"x", 1, 300).is_err(), "n > 255");
225 assert!(reconstruct(&[]).is_err());
226 let s = split(b"xy", 2, 3).unwrap();
227 let dup = vec![s[0].clone(), s[0].clone()];
229 assert!(reconstruct(&dup).is_err());
230 }
231
232 #[test]
233 fn serde_round_trips() {
234 let shares = split(b"round trip", 2, 3).unwrap();
235 let json = serde_json::to_string(&shares).unwrap();
236 let back: Vec<Share> = serde_json::from_str(&json).unwrap();
237 assert_eq!(shares, back);
238 assert_eq!(reconstruct(&back[..2]).unwrap(), b"round trip");
239 }
240}