use sha2::{Digest, Sha256};
#[inline]
fn gf_add(a: u8, b: u8) -> u8 {
a ^ b
}
fn gf_mul(mut a: u8, mut b: u8) -> u8 {
let mut p: u8 = 0;
while a > 0 && b > 0 {
if b & 1 == 1 {
p ^= a;
}
if (a & 0x80) != 0 {
a = (a << 1) ^ 0x1B;
} else {
a <<= 1;
}
b >>= 1;
}
p
}
fn gf_pow(base: u8, exp: u8) -> u8 {
if exp == 0 {
return 1;
}
let mut res: u8 = 1;
let mut b = base;
let mut e = exp;
while e > 0 {
if e & 1 == 1 {
res = gf_mul(res, b);
}
b = gf_mul(b, b);
e >>= 1;
}
res
}
fn gf_inv(n: u8) -> u8 {
if n == 0 {
panic!("Division by zero in GF(2^8): cannot invert 0.");
}
gf_pow(n, 254)
}
fn gf_div(num: u8, den: u8) -> u8 {
if den == 0 {
panic!("Division by zero in GF(2^8): denominator is 0.");
}
gf_mul(num, gf_inv(den))
}
fn poly_eval_horner(coeffs: &[u8], x: u8) -> u8 {
let mut res: u8 = 0;
for i in (0..coeffs.len()).rev() {
res = gf_mul(res, x);
res = gf_add(res, coeffs[i]);
}
res
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Share {
pub x: u8,
pub ys: Vec<u8>,
}
pub fn split_secret(secret: &[u8], k: usize, x_coords: &[u8]) -> Result<Vec<Share>, String> {
let n = x_coords.len();
if k == 0 {
return Err("Threshold k cannot be 0.".to_string());
}
if k > n {
return Err(format!(
"Threshold k ({}) cannot be greater than number of shares n ({}).",
k, n
));
}
if secret.is_empty() {
return Err("Secret cannot be empty.".to_string());
}
if x_coords.contains(&0) {
return Err("x-coordinates cannot be zero.".to_string());
}
let mut sorted_x = x_coords.to_vec();
sorted_x.sort_unstable();
if (1..sorted_x.len()).any(|i| sorted_x[i - 1] == sorted_x[i]) {
return Err("x-coordinates must be distinct.".to_string());
}
let mut shares_data: Vec<Share> = Vec::with_capacity(n);
for &x_val in x_coords {
shares_data.push(Share {
x: x_val,
ys: Vec::with_capacity(secret.len()),
});
}
for &secret_byte in secret {
let mut coeffs = vec![0u8; k];
coeffs[0] = secret_byte;
if k > 1 {
let mut hasher = Sha256::new();
hasher.update([secret_byte]);
hasher.update(x_coords);
let result = hasher.finalize();
for i in 1..k {
coeffs[i] = result[i % result.len()];
}
}
for share in shares_data.iter_mut() {
let y_val = poly_eval_horner(&coeffs, share.x);
share.ys.push(y_val);
}
}
Ok(shares_data)
}
pub fn reconstruct_secret(shares: &[Share], k: usize) -> Result<Vec<u8>, String> {
if k == 0 {
return Err("Threshold k cannot be 0.".to_string());
}
if shares.len() < k {
return Err(format!(
"Not enough shares provided (need at least {}, got {}).",
k,
shares.len()
));
}
if shares.is_empty() {
return Err("Shares list cannot be empty.".to_string());
}
let relevant_shares = &shares[0..k];
let num_bytes_in_secret = relevant_shares[0].ys.len();
if num_bytes_in_secret == 0 {
return Err(
"Shares indicate an empty original secret or are malformed (ys vector is empty)."
.to_string(),
);
}
let mut distinct_x_coords = Vec::with_capacity(k);
for share in relevant_shares.iter() {
if share.x == 0 {
return Err("Share x-coordinate cannot be zero.".to_string());
}
if share.ys.len() != num_bytes_in_secret {
return Err("All shares used for reconstruction must have the same number of y-values (same secret length).".to_string());
}
if distinct_x_coords.contains(&share.x) {
return Err(
"x-coordinates of shares used for reconstruction must be distinct.".to_string(),
);
}
distinct_x_coords.push(share.x);
}
let mut reconstructed_secret_bytes: Vec<u8> = Vec::with_capacity(num_bytes_in_secret);
for byte_idx in 0..num_bytes_in_secret {
let mut current_secret_byte_sum: u8 = 0;
for j in 0..k {
let x_j = relevant_shares[j].x;
let y_j = relevant_shares[j].ys[byte_idx];
let mut lagrange_basis_poly_at_0: u8 = 1;
for (m, relevant_share) in relevant_shares.iter().enumerate().take(k) {
if m == j {
continue;
}
let x_m = relevant_share.x;
let num_term = x_m;
let den_term = gf_add(x_j, x_m);
lagrange_basis_poly_at_0 =
gf_mul(lagrange_basis_poly_at_0, gf_div(num_term, den_term));
}
current_secret_byte_sum = gf_add(
current_secret_byte_sum,
gf_mul(y_j, lagrange_basis_poly_at_0),
);
}
reconstructed_secret_bytes.push(current_secret_byte_sum);
}
Ok(reconstructed_secret_bytes)
}
#[cfg(test)]
pub mod test_helpers {
use super::*;
pub fn gf_mul_test(a: u8, b: u8) -> u8 {
gf_mul(a, b)
}
pub fn gf_inv_test(n: u8) -> u8 {
gf_inv(n)
}
pub fn poly_eval_horner_test(coeffs: &[u8], x: u8) -> u8 {
poly_eval_horner(coeffs, x)
}
}
#[cfg(test)]
mod tests {
use super::test_helpers::{gf_inv_test, gf_mul_test, poly_eval_horner_test};
use super::*;
use rand::{Rng, seq::SliceRandom};
#[test]
fn test_gf_mul_basic() {
assert_eq!(gf_mul_test(0x53, 0xCA), 0x01); assert_eq!(gf_mul_test(0x02, 0x80), 0x1B); assert_eq!(gf_mul_test(0xFF, 0xFF), 0x13);
assert_eq!(gf_mul_test(0x01, 0xAB), 0xAB);
assert_eq!(gf_mul_test(0x00, 0xAB), 0x00);
}
#[test]
fn test_gf_inv_basic() {
assert_eq!(gf_inv_test(1), 1);
assert_eq!(gf_inv_test(0x53), 0xCA);
assert_eq!(gf_inv_test(0xCA), 0x53);
assert_eq!(gf_mul_test(0x02, gf_inv_test(0x02)), 0x01);
}
#[test]
#[should_panic]
fn test_gf_inv_zero() {
gf_inv_test(0);
}
#[test]
fn test_poly_eval_horner_examples() {
let coeffs = vec![0x05, 0x01, 0x03]; assert_eq!(poly_eval_horner_test(&coeffs, 0x02), 0x0B);
let coeffs_const = vec![0xAA]; assert_eq!(poly_eval_horner_test(&coeffs_const, 0x10), 0xAA);
assert_eq!(poly_eval_horner_test(&coeffs, 0x00), coeffs[0]);
assert_eq!(poly_eval_horner_test(&coeffs_const, 0x00), coeffs_const[0]);
}
#[test]
fn test_shamir_e2e_simple() {
let secret = b"hello world".to_vec();
let k = 3;
let x_coords: Vec<u8> = vec![1, 2, 3, 4, 5];
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
assert_eq!(shares.len(), 5);
for share in &shares {
assert_eq!(share.ys.len(), secret.len());
}
let chosen_shares_k = shares[0..k].to_vec();
let reconstructed_k_result = reconstruct_secret(&chosen_shares_k, k);
assert!(reconstructed_k_result.is_ok());
assert_eq!(reconstructed_k_result.unwrap(), secret);
let chosen_shares_k_alt = vec![shares[0].clone(), shares[2].clone(), shares[4].clone()];
let reconstructed_k_alt_result = reconstruct_secret(&chosen_shares_k_alt, k);
assert!(reconstructed_k_alt_result.is_ok());
assert_eq!(reconstructed_k_alt_result.unwrap(), secret);
let mut rng = rand::rng();
let mut shuffled_shares = shares.clone();
shuffled_shares.shuffle(&mut rng);
let reconstructed_all_result = reconstruct_secret(&shuffled_shares, k);
assert!(reconstructed_all_result.is_ok());
assert_eq!(reconstructed_all_result.unwrap(), secret);
}
#[test]
fn test_shamir_k_equals_n() {
let secret = b"k_equals_n_test".to_vec();
let k = 4;
let x_coords: Vec<u8> = vec![10, 20, 30, 40];
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
assert_eq!(shares.len(), k);
let reconstructed_secret_result = reconstruct_secret(&shares, k);
assert!(reconstructed_secret_result.is_ok());
assert_eq!(reconstructed_secret_result.unwrap(), secret);
}
#[test]
fn test_shamir_k1_n1() {
let secret = vec![0x42u8]; let k = 1;
let x_coords: Vec<u8> = vec![1];
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
assert_eq!(shares.len(), 1);
assert_eq!(shares[0].x, 1);
assert_eq!(shares[0].ys[0], secret[0]);
let reconstructed_secret_result = reconstruct_secret(&shares, k);
assert!(reconstructed_secret_result.is_ok());
assert_eq!(reconstructed_secret_result.unwrap(), secret);
}
#[test]
fn test_shamir_k1_n_greater_than_1() {
let secret = b"S".to_vec();
let k = 1;
let x_coords: Vec<u8> = vec![5, 10, 15];
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
assert_eq!(shares.len(), 3);
for share in &shares {
assert_eq!(share.ys[0], secret[0]); }
let reconstructed_1 = reconstruct_secret(&[shares[0].clone()], k);
assert_eq!(reconstructed_1.unwrap(), secret);
let reconstructed_2 = reconstruct_secret(&[shares[1].clone()], k);
assert_eq!(reconstructed_2.unwrap(), secret);
let reconstructed_3 = reconstruct_secret(&[shares[2].clone()], k);
assert_eq!(reconstructed_3.unwrap(), secret);
}
#[test]
fn test_split_invalid_inputs() {
let secret = b"s".to_vec();
assert!(split_secret(&secret, 0, &[1]).is_err(), "k=0");
assert!(split_secret(&secret, 3, &[1, 2]).is_err(), "k > n");
assert!(split_secret(&[], 1, &[1]).is_err(), "empty secret");
assert!(split_secret(&secret, 1, &[0]).is_err(), "x_coord = 0");
assert!(
split_secret(&secret, 2, &[1, 1]).is_err(),
"duplicate x_coords"
);
}
#[test]
fn test_reconstruct_invalid_inputs() {
let share1 = Share {
x: 1,
ys: vec![10, 20],
};
let share2 = Share {
x: 2,
ys: vec![30, 40],
};
let share_malformed_len = Share { x: 3, ys: vec![50] }; let share_x_zero = Share {
x: 0,
ys: vec![10, 20],
};
let share_empty_ys = Share { x: 4, ys: vec![] };
assert!(
reconstruct_secret(&[share1.clone(), share2.clone()], 0).is_err(),
"k=0"
);
assert!(
reconstruct_secret(&[share1.clone()], 2).is_err(),
"not enough shares"
);
assert!(reconstruct_secret(&[], 1).is_err(), "empty shares list");
assert!(
reconstruct_secret(&[share1.clone(), share_malformed_len], 2).is_err(),
"inconsistent ys length"
);
assert!(
reconstruct_secret(&[share1.clone(), share_x_zero], 2).is_err(),
"share with x=0"
);
assert!(
reconstruct_secret(&[share1.clone(), share_empty_ys], 2).is_err(),
"share with empty ys"
);
assert!(
reconstruct_secret(&[share1.clone(), share1.clone()], 2).is_err(),
"duplicate x-coordinates"
);
}
#[test]
fn test_large_secret() {
let mut secret = Vec::with_capacity(1000);
let mut rng = rand::rng();
for _ in 0..1000 {
secret.push(rng.random::<u8>());
}
let k = 5;
let n = 10;
let mut x_coords = Vec::with_capacity(n);
for i in 1..=n {
x_coords.push(i as u8);
}
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
let mut rng = rand::rng();
let mut indices: Vec<usize> = (0..n).collect();
indices.shuffle(&mut rng);
let selected_indices = indices[0..k].to_vec();
let mut selected_shares = Vec::with_capacity(k);
for idx in selected_indices {
selected_shares.push(shares[idx].clone());
}
let reconstructed_result = reconstruct_secret(&selected_shares, k);
assert!(reconstructed_result.is_ok());
assert_eq!(reconstructed_result.unwrap(), secret);
}
#[test]
fn test_binary_data() {
let mut secret = Vec::with_capacity(256);
for i in 0..=255u8 {
secret.push(i);
}
let k = 3;
let n = 5;
let x_coords: Vec<u8> = (1..=n as u8).collect();
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
let chosen_shares = shares[1..4].to_vec(); let reconstructed_result = reconstruct_secret(&chosen_shares, k);
assert!(reconstructed_result.is_ok());
assert_eq!(reconstructed_result.unwrap(), secret);
}
#[test]
fn test_k_equals_2() {
let secret = b"linear polynomial test".to_vec();
let k = 2;
let x_coords: Vec<u8> = vec![1, 2, 3, 4, 5];
let shares_result = split_secret(&secret, k, &x_coords);
assert!(shares_result.is_ok());
let shares = shares_result.unwrap();
let combinations = vec![vec![0, 1], vec![1, 3], vec![3, 4], vec![0, 4]];
for combo in combinations {
let mut selected_shares = Vec::with_capacity(k);
for &idx in &combo {
selected_shares.push(shares[idx].clone());
}
let reconstructed_result = reconstruct_secret(&selected_shares, k);
assert!(reconstructed_result.is_ok());
assert_eq!(reconstructed_result.unwrap(), secret);
}
}
}