diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cafcbba..9d8fd66 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -52,8 +52,8 @@ jobs: - macOS-latest rust: - stable - - beta - - nightly + # - beta + # - nightly runs-on: ${{ matrix.os }} needs: [compile] steps: diff --git a/Cargo.toml b/Cargo.toml index fbcc013..d190a33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ members = [ "polynomial", "univariate-polynomial-iop-zerotest", - "halo2-trials" + "halo2-trials", + "shamir-secret-sharing" ] resolver = "2" diff --git a/polynomial/Cargo.toml b/polynomial/Cargo.toml index 80538bf..b8f812d 100644 --- a/polynomial/Cargo.toml +++ b/polynomial/Cargo.toml @@ -5,3 +5,4 @@ version = "0.1.0" [dependencies] num-traits = "0.2.18" +nalgebra = "0.32.5" diff --git a/polynomial/src/lib.rs b/polynomial/src/lib.rs index 6ad3518..4ec4662 100644 --- a/polynomial/src/lib.rs +++ b/polynomial/src/lib.rs @@ -4,7 +4,8 @@ use std::{ hash::Hash, ops::{Mul, Sub}, }; - +extern crate nalgebra as na; +use na::{ComplexField, DMatrix, RowDVector, Scalar}; use num_traits::{One, Zero}; pub enum PolynomialRepr { @@ -60,7 +61,65 @@ where } } -impl + Sub + Clone + Debug> Polynomial { +impl Polynomial +where + T: One + Clone, +{ + /// Generate powers of `base` raised to a max degree of `deg-1` + fn generate_powers(base: T, deg: usize) -> Vec { + let mut powers_vec = Vec::::with_capacity(deg); + powers_vec.push(T::one()); + for idx in 1..deg { + powers_vec.push(base.clone() * powers_vec[idx - 1].clone()); + } + powers_vec + } +} + +impl Polynomial +where + T: Clone + Scalar + Debug + Mul + One + ComplexField, +{ + /// Generate a polynomials from its evalutation points givent in + /// a tuple format `(a, b)` such that `poly(a) = b`. Given `n` + /// points of evaluation, `n-1` degree polynomial is generated + pub fn new_from_evals(evals: &[(T, T)]) -> Self { + // We know that if all evalutaions matrix `E` is multiplied by + // coefficient vector `C`, resultant would be `R`. `evals` + // essentially is `[E(x) | R]` + let x_powers_matrix = DMatrix::::from_rows( + &evals + .iter() + .map(|(eval_point, _eval)| { + RowDVector::from_iterator( + evals.len(), + Self::generate_powers(eval_point.clone(), evals.len()), + ) + }) + .collect::>()[..], + ); + + let x_inverse_matrix = x_powers_matrix.qr().try_inverse().unwrap(); + + let eval_matrix = DMatrix::::from_rows( + &evals + .iter() + .map(|(_eval_point, eval)| RowDVector::from_iterator(1, [eval.clone()])) + .collect::>()[..], + ); + + let coeff_matrix = x_inverse_matrix * eval_matrix; + + Self { + repr: PolynomialRepr::Coeff(Vec::::from_iter(coeff_matrix.iter().cloned())), + } + } +} + +impl Polynomial +where + T: Zero + One + Mul + Sub + Clone + Debug, +{ /// Evaluates the polynomial at a point. /// /// # Examples @@ -120,4 +179,17 @@ mod tests { assert_eq!(Polynomial::::new_from_roots(&[3, 2, 1]).degree(), 3); assert_eq!(Polynomial::::new_from_roots(&[3, 2, 3]).degree(), 2); } + + #[test] + fn test_generate_power() { + assert_eq!(Polynomial::generate_powers(2, 5), vec![1, 2, 4, 8, 16]); + } + + #[test] + fn polynomial_from_evals() { + // polynomial -> 1 + 4*x + x^2 + let evals = [(1.0, 6.0), (2.0, 13.0), (3.0, 22.0)]; + let polynomial = Polynomial::new_from_evals(&evals); + assert_eq!(polynomial.eval(10.0) as i64, 141); + } } diff --git a/shamir-secret-sharing/Cargo.toml b/shamir-secret-sharing/Cargo.toml new file mode 100644 index 0000000..18bdc63 --- /dev/null +++ b/shamir-secret-sharing/Cargo.toml @@ -0,0 +1,9 @@ +[package] +edition = "2021" +name = "shamir-secret-sharing" +version = "0.1.0" + +[dependencies] +polynomial = {path = "../polynomial"} +rand = "0.8.5" +rand_chacha = "0.3.1" diff --git a/shamir-secret-sharing/README.md b/shamir-secret-sharing/README.md new file mode 100644 index 0000000..e69de29 diff --git a/shamir-secret-sharing/src/lib.rs b/shamir-secret-sharing/src/lib.rs new file mode 100644 index 0000000..3b2dc10 --- /dev/null +++ b/shamir-secret-sharing/src/lib.rs @@ -0,0 +1,53 @@ +#[cfg(test)] +mod tests { + use polynomial::Polynomial; + use rand::prelude::*; + + #[test] + fn shamir_secret_sharing() { + // Let's say we want to create a shamir secret scheme + // for hiding S = 119 + let secret = 119; + + // Assume that we want to break secret into 4 "parts", + // of which any 2 should be able to reconstruct the + // original secret + let parts = 4; + let threshold = 2; + + // Hence, coefficients will be of form `secret + r1*x + r2*x^2...` + // where `r` is random values from `(0, threshold)` + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0); + let coeffs: Vec = [secret as f64] + .into_iter() + .chain((0..threshold - 1).map(|_| rng.next_u32() as f64)) + .collect(); + + let polynomial = Polynomial::new_from_coeffs(&coeffs); + + // Ensure that we can get the secret back given we evaluate + // at 0 + assert_eq!(polynomial.eval(0.0) as i64, secret); + + // Now evaulate at a few random points to make the secret + let secret_parts: Vec<(f64, f64)> = (0..parts) + .map(|_| { + let point_of_eval = rng.next_u32() as f64; + let eval = polynomial.eval(point_of_eval); // + 1.0 required as we do not want to generate at 0 + (point_of_eval, eval) + }) + .collect(); + + // Ensure reconstruction is possible + let reconstructed_poly = Polynomial::new_from_evals(&[secret_parts[0], secret_parts[3]]); + assert!(reconstructed_poly.eval(0.0) - (secret as f64) < 0.0000001); // due to f64 inaccuracy + + // Ensure reconstruction is possible + let reconstructed_poly = Polynomial::new_from_evals(&[secret_parts[1], secret_parts[3]]); + assert!(reconstructed_poly.eval(0.0) - (secret as f64) < 0.0000001); // due to f64 inaccuracy + + // Ensure reconstruction is possible + let reconstructed_poly = Polynomial::new_from_evals(&[secret_parts[1], secret_parts[2]]); + assert!(reconstructed_poly.eval(0.0) - (secret as f64) < 0.0000001); // due to f64 inaccuracy + } +}