Skip to content

Commit

Permalink
Shamir Secret Sharing refactor (#8)
Browse files Browse the repository at this point in the history
* workspace based deps

* Taplo FMT

* found bug: shamir secret sharing

* working: low f64 SSS algo and FSSS

* fix: ssv

* fix: commented Fel87

* fix: CI
  • Loading branch information
supragya authored Apr 9, 2024
1 parent c98ae2b commit a9c9566
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 70 deletions.
23 changes: 12 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,31 @@ env:

jobs:
lint:
strategy:
matrix:
rust: [stable, nightly]
name: Lint
runs-on: ubuntu-latest
steps:
- name: Setup | Checkout
uses: actions/checkout@v2
- name: Setup | Rust
uses: ATiltedTree/setup-rust@v1
- uses: hecrj/setup-rust-action@v2
with:
rust-version: stable
components: clippy
rust-version: ${{ matrix.rust }}
- name: Build | Lint
run: cargo clippy
compile:
strategy:
matrix:
rust: [stable, nightly]
name: Compile
runs-on: ubuntu-latest
steps:
- name: Setup | Checkout
uses: actions/checkout@v2
- name: Setup | Rust
uses: ATiltedTree/setup-rust@v1
- uses: hecrj/setup-rust-action@v2
with:
rust-version: stable
rust-version: ${{ matrix.rust }}
- name: Build | Compile
run: cargo check
test:
Expand All @@ -52,15 +55,13 @@ jobs:
- macOS-latest
rust:
- stable
# - beta
# - nightly
- nightly
runs-on: ${{ matrix.os }}
needs: [compile]
steps:
- name: Setup | Checkout
uses: actions/checkout@v2
- name: Setup | Rust
uses: ATiltedTree/setup-rust@v1
- uses: hecrj/setup-rust-action@v2
with:
rust-version: ${{ matrix.rust }}
- name: Build | Compile
Expand Down
23 changes: 19 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
[workspace]
members = [
"polynomial",
"univariate-polynomial-iop-zerotest",
"halo2-trials",
"[Sha97]shamir-secret-sharing"
"polynomial",
"univariate-polynomial-iop-zerotest",
"halo2-trials",
"[Sha97]shamir-secret-sharing",
"[Fel87]feldman-verifiable-secret-sharing",
]
resolver = "2"

[profile.release]
codegen-units = 1
lto = "fat"
opt-level = "z"

[workspace.dependencies]
ark-bls12-381 = "0.4.0"
ark-ec = "0.4.2"
ark-ff = "0.4.2"
ark-std = "0.4.0"
num-traits = "0.2.18"
nalgebra = "0.32.5"
halo2 = "0.0.0"
halo2_proofs = "0.3.0"
rand_core = "0.6.4"
rand = "0.8.5"
rand_chacha = "0.3.1"
simba = "0.8.1"
13 changes: 13 additions & 0 deletions [Fel87]feldman-verifiable-secret-sharing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
edition = "2021"
name = "feldman-verifiable-secret-sharing"
version = "0.1.0"

[dependencies]
polynomial = { path = "../polynomial" }
rand = { workspace = true }
rand_chacha = { workspace = true }
ark-bls12-381 = { workspace = true }
ark-ec = { workspace = true }
ark-ff = { workspace = true }
ark-std = { workspace = true }
Empty file.
106 changes: 106 additions & 0 deletions [Fel87]feldman-verifiable-secret-sharing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use ark_bls12_381::G1Projective;
use ark_ec::Group;

#[allow(dead_code)]
fn generate_two_powers(point: G1Projective, n: usize) -> Vec<G1Projective> {
if n < 2 {
panic!();
}
let mut powers: Vec<G1Projective> = Vec::with_capacity(n);
powers.push(G1Projective::default());
powers.push(point);
for idx in 2..n {
powers.push(powers[idx - 1].double());
}
powers
}

#[allow(dead_code)]
fn point_exponentiation(exponent: u32, powers_of_2: &Vec<G1Projective>) -> G1Projective {
// Initialize the result to the identity element (zero point) of the group
let mut result = G1Projective::default();

// Iterate through each bit of the exponent
for i in 0..32 {
if (exponent >> i) & 1 == 1 {
// If the i-th bit is set in the exponent, add the corresponding power_of_2 to the result
result += &powers_of_2[i as usize];
}
}

result
}

#[cfg(test)]
mod tests {
// use ark_ec::Group;
// use polynomial::Polynomial;
// // use rand::prelude::*;
// use super::*;

// #[test]
// fn feldman_verifiable_secret_sharing_low_f64() {
// // Let's say we want to create a verifyable secret scheme
// // for hiding S = 5
// let secret = 5;

// // 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 coeffs: Vec<u32> = vec![secret as u32, 4];

// let generator_two_powers =
// generate_two_powers(ark_bls12_381::G1Projective::generator(), 32);

// let public_g_raised_coeffs: Vec<G1Projective> = coeffs
// .iter()
// .map(|x| point_exponentiation(*x, &generator_two_powers))
// .collect();

// let coeffs: Vec<f64> = coeffs.into_iter().map(|x| x 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 f64, secret as f64);

// // Now evaulate at a few random points to make the secret
// let secret_parts: Vec<(f64, f64)> = (1..(parts + 1))
// .map(|x| {
// let eval = polynomial.eval((x * 10) as f64);
// ((x * 10) as f64, eval)
// })
// .collect();

// // Ensure reconstruction is possible
// let secrets_revealed = [secret_parts[0], secret_parts[3]];
// let reconstructed_poly = Polynomial::new_from_evals(&secrets_revealed);
// // Now that we have reconstructed the polynomial from secret parts,
// // we need to ensure that those "secrets" are verifyably correct.
// // This is where VSS kicks in. For this, we use `public_g_raised_coeffs`
// let reconstructed_coeffs: Vec<u32> = reconstructed_poly
// .get_raw_coefficients()
// .into_iter()
// .map(|x| x as u32)
// .collect();

// let a_powers: Vec<Vec<G1Projective>> = public_g_raised_coeffs
// .into_iter()
// .map(|x| generate_two_powers(x, 32))
// .collect();

// for (x, eval) in secrets_revealed {
// let mut running_x_pow = 1.0;
// let mut running_g_multiplier = G1Projective::default();
// for (idx, coeff) in reconstructed_coeffs.iter().enumerate() {
// running_g_multiplier =
// running_g_multiplier * point_exponentiation(*coeff, &a_powers[idx])
// }
// }

// // assert!((reconstructed_poly.eval(0.0) - (secret as f64)).abs() < 0.0000001);
// }
}
8 changes: 5 additions & 3 deletions [Sha97]shamir-secret-sharing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ name = "shamir-secret-sharing"
version = "0.1.0"

[dependencies]
polynomial = {path = "../polynomial"}
rand = "0.8.5"
rand_chacha = "0.3.1"
num-traits = { workspace = true }
simba = { workspace = true }
polynomial = { path = "../polynomial" }
rand = { workspace = true }
rand_chacha = { workspace = true }
133 changes: 94 additions & 39 deletions [Sha97]shamir-secret-sharing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,108 @@
#[cfg(test)]
mod tests {
use polynomial::Polynomial;
use rand::prelude::*;
use std::fmt::Debug;
use std::ops::Mul;
use std::ops::Sub;

#[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<f64> = [secret as f64]
use num_traits::One;
use num_traits::Zero;
use polynomial::Polynomial;
use rand::RngCore;
use rand::SeedableRng;
use simba::scalar::ComplexField;

/// Shamir's secret sharing is an ideal and perfect and ideal
/// (k, n)-threshold scheme based on polynomial interpolation.
/// Here, `n` is the number of secret parts generated by the
/// scheme, knowledge of any `k` of which can reconstruct original
/// secret
pub struct ShamirSecret<T, const K: usize, const N: usize> {
/// The underlying secret value to be broken up
/// into secret "parts"
secret: T,
}

/// A share of secret of form `(x, f(x))` where `x` is "evalutation
/// point" and `f(x)` is "opening"
#[derive(Debug, Clone)]
pub struct SecretShare<T> {
evaluation_point: T,
opening: T,
}

impl<T, const K: usize, const N: usize> ShamirSecret<T, K, N>
where
T: From<u32> + Debug + Clone + Zero + One + Mul<Output = T> + Sub<Output = T> + ComplexField,
{
/// Generates a Shamir's secret generator for a given
/// secret value `secret` such that any `k` of `n` shares
/// generated can reconstruct the `secret` back
pub fn new_from_secret(secret: T) -> Self {
assert!(K < N);
Self { secret }
}

/// Generate a Shamir's secret from `K` shares
pub fn new_from_shares(shares: [SecretShare<T>; K]) -> Self {
let evaluations: Vec<(T, T)> = shares
.into_iter()
.chain((0..threshold - 1).map(|_| rng.next_u32() as f64))
.map(|s| (s.evaluation_point, s.opening))
.collect();
let reconstructed_poly = Polynomial::new_from_evals(&evaluations);

let polynomial = Polynomial::new_from_coeffs(&coeffs);
Self {
secret: reconstructed_poly.eval(T::zero()),
}
}

// Ensure that we can get the secret back given we evaluate
// at 0
assert_eq!(polynomial.eval(0.0) as i64, secret);
/// Get the underlying secret value
pub fn get_secret(&self) -> T {
self.secret.clone()
}

// Now evaulate at a few random points to make the secret
let secret_parts: Vec<(f64, f64)> = (0..parts)
/// Generate `n` secret shares
pub fn generate_secret_shares(&self) -> [SecretShare<T>; N] {
// first, generate a polynomial of form `f(x) = s + a*x + b*x^2 + ...`
// where `s` is the encoded secret value, `a`, `b`... are random
// coefficients and degree of `f(x)` is `K-1`.
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);

let coefficients: Vec<T> = vec![self.secret.clone()]
.into_iter()
.chain((1..K).map(|_| T::from(rng.next_u32() % 100)))
.collect();

let polynomial = Polynomial::new_from_coeffs(&coefficients);

let secret_shares: Vec<SecretShare<T>> = (0..N)
.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)
let evaluation_point = T::from(rng.next_u32() % 80);
let opening = polynomial.eval(evaluation_point.clone());
SecretShare {
evaluation_point,
opening,
}
})
.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
secret_shares
.try_into()
.expect("slice with incorrect length")
}
}

#[cfg(test)]
mod tests {
use crate::ShamirSecret;

#[test]
fn shamir_secret_sharing_basic() {
let secret_value = 2;

let shares = ShamirSecret::<f64, 2, 4>::new_from_secret(secret_value.into())
.generate_secret_shares();

// 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
let reconstructed_from_shares =
ShamirSecret::<f64, 2, 4>::new_from_shares([shares[0].clone(), shares[2].clone()]);

// 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
assert_eq!(reconstructed_from_shares.get_secret() as i32, secret_value)
}
}
6 changes: 3 additions & 3 deletions halo2-trials/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ name = "halo2-trials"
version = "0.1.0"

[dependencies]
halo2 = "0.0.0"
halo2_proofs = "0.3.0"
rand_core = "0.6.4"
halo2 = { workspace = true }
halo2_proofs = { workspace = true }
rand_core = { workspace = true }
4 changes: 2 additions & 2 deletions polynomial/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ name = "polynomial"
version = "0.1.0"

[dependencies]
num-traits = "0.2.18"
nalgebra = "0.32.5"
num-traits = { workspace = true }
nalgebra = { workspace = true }
Loading

0 comments on commit a9c9566

Please sign in to comment.