diff --git a/CHANGELOG.md b/CHANGELOG.md index f35bba6..1dca2df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - 2020-01-22 +### Added +- Share struct which allows to convert from/to byte vectors + +### Changed +- Methods use the new Share struct, instead of (GF245, Vec) tuples + ## [0.2.0] - 2020-01-21 ### Added - Computations performed over GF256 (much faster) diff --git a/Cargo.toml b/Cargo.toml index 0261c2e..d94773c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sharks" -version = "0.2.0" +version = "0.3.0" authors = ["Aitor Ruano "] description = "Fast, small and secure Shamir's Secret Sharing library crate" homepage = "https://github.com/c0dearm/sharks" diff --git a/README.md b/README.md index 8caa3c1..8adcfd7 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ You can run them with `cargo test` and `cargo bench`. ### Benchmark results [min mean max] -| CPU | obtain_shares_dealer | step_shares_dealer | recover_secret | -| ----------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | -| Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz | [1.4321 us 1.4339 us 1.4357 us] | [1.3385 ns 1.3456 ns 1.3552 ns] | [228.77 us 232.17 us 236.23 us] | +| CPU | obtain_shares_dealer | step_shares_dealer | recover_secret | share_from_bytes | share_to_bytes | +| ----------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | +| Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz | [1.4321 us 1.4339 us 1.4357 us] | [1.3385 ns 1.3456 ns 1.3552 ns] | [228.77 us 232.17 us 236.23 us] | [24.688 ns 25.083 ns 25.551 ns] | [22.832 ns 22.910 ns 22.995 ns] | # Contributing diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index c82a426..2e2d60a 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,6 +1,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use sharks::Sharks; +use sharks::{Share, Sharks}; fn dealer(c: &mut Criterion) { let sharks = Sharks(255); @@ -14,12 +14,26 @@ fn dealer(c: &mut Criterion) { fn recover(c: &mut Criterion) { let sharks = Sharks(255); - let shares = sharks.dealer(&[1]).take(255).collect(); + let shares: Vec = sharks.dealer(&[1]).take(255).collect(); c.bench_function("recover_secret", |b| { - b.iter(|| sharks.recover(black_box(&shares))) + b.iter(|| sharks.recover(black_box(shares.as_slice()))) }); } -criterion_group!(benches, dealer, recover); +fn share(c: &mut Criterion) { + let bytes_vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let bytes = bytes_vec.as_slice(); + let share = Share::from(bytes); + + c.bench_function("share_from_bytes", |b| { + b.iter(|| Share::from(black_box(bytes))) + }); + + c.bench_function("share_to_bytes", |b| { + b.iter(|| Vec::from(black_box(&share))) + }); +} + +criterion_group!(benches, dealer, recover, share); criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 79b2b80..0580124 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,40 +2,42 @@ //! //! Usage example: //! ``` -//! use sharks::Sharks; +//! use sharks::{ Sharks, Share }; //! //! // Set a minimum threshold of 10 shares //! let sharks = Sharks(10); //! // Obtain an iterator over the shares for secret [1, 2, 3, 4] //! let dealer = sharks.dealer(&[1, 2, 3, 4]); //! // Get 10 shares -//! let shares = dealer.take(10).collect(); +//! let shares: Vec = dealer.take(10).collect(); //! // Recover the original secret! -//! let secret = sharks.recover(&shares).unwrap(); +//! let secret = sharks.recover(shares.as_slice()).unwrap(); //! assert_eq!(secret, vec![1, 2, 3, 4]); //! ``` mod field; mod math; +mod share; -use std::collections::HashMap; +use std::collections::HashSet; use field::GF256; +pub use share::Share; /// Tuple struct which implements methods to generate shares and recover secrets over a 256 bits Galois Field. /// Its only parameter is the minimum shares threshold. /// /// Usage example: /// ``` -/// # use sharks::Sharks; +/// # use sharks::{ Sharks, Share }; /// // Set a minimum threshold of 10 shares /// let sharks = Sharks(10); /// // Obtain an iterator over the shares for secret [1, 2, 3, 4] /// let dealer = sharks.dealer(&[1, 2, 3, 4]); /// // Get 10 shares -/// let shares = dealer.take(10).collect(); +/// let shares: Vec = dealer.take(10).collect(); /// // Recover the original secret! -/// let secret = sharks.recover(&shares).unwrap(); +/// let secret = sharks.recover(shares.as_slice()).unwrap(); /// assert_eq!(secret, vec![1, 2, 3, 4]); /// ``` pub struct Sharks(pub u8); @@ -46,14 +48,13 @@ impl Sharks { /// /// Example: /// ``` - /// # use std::collections::HashMap; - /// # use sharks::Sharks; + /// # use sharks::{ Sharks, Share }; /// # let sharks = Sharks(3); /// // Obtain an iterator over the shares for secret [1, 2] /// let dealer = sharks.dealer(&[1, 2]); /// // Get 3 shares - /// let shares: HashMap<_, _> = dealer.take(3).collect(); - pub fn dealer(&self, secret: &[u8]) -> impl Iterator)> { + /// let shares: Vec = dealer.take(3).collect(); + pub fn dealer(&self, secret: &[u8]) -> impl Iterator { let mut polys = Vec::with_capacity(secret.len()); for chunk in secret { @@ -63,26 +64,28 @@ impl Sharks { math::get_evaluator(polys) } - /// Given a `HashMap` of shares, recovers the original secret. - /// If the number of shares is less than the minimum threshold an `Err` is returned, + /// Given a slice of shares, recovers the original secret. + /// If the number of distinct shares is less than the minimum threshold an `Err` is returned, /// otherwise an `Ok` containing the secret. /// /// Example: /// ``` - /// # use sharks::Sharks; + /// # use sharks::{ Sharks, Share }; /// # let sharks = Sharks(3); - /// # let mut shares = sharks.dealer(&[1]).take(3).collect(); - /// // Revover original secret from shares + /// # let mut shares: Vec = sharks.dealer(&[1]).take(3).collect(); + /// // Recover original secret from shares /// let mut secret = sharks.recover(&shares); /// // Secret correctly recovered /// assert!(secret.is_ok()); - /// // Remove shares for demonstrastion purposes + /// // Remove shares for demonstration purposes /// shares.clear(); /// secret = sharks.recover(&shares); /// // Not enough shares to recover secret /// assert!(secret.is_err()); - pub fn recover(&self, shares: &HashMap>) -> Result, &str> { - if shares.len() < self.0 as usize { + pub fn recover(&self, shares: &[Share]) -> Result, &str> { + let shares_x: HashSet = shares.iter().map(|s| s.x.0).collect(); + + if shares_x.len() < self.0 as usize { Err("Not enough shares to recover original secret") } else { Ok(math::interpolate(shares)) @@ -92,13 +95,26 @@ impl Sharks { #[cfg(test)] mod tests { - use super::{Sharks, GF256}; + use super::{Share, Sharks}; #[test] fn test_insufficient_shares_err() { let sharks = Sharks(255); let dealer = sharks.dealer(&[1]); - let shares = dealer.take(254).collect(); + let shares: Vec = dealer.take(254).collect(); + let secret = sharks.recover(&shares); + assert!(secret.is_err()); + } + + #[test] + fn test_duplicate_shares_err() { + let sharks = Sharks(255); + let dealer = sharks.dealer(&[1]); + let mut shares: Vec = dealer.take(255).collect(); + shares[1] = Share { + x: shares[0].x, + y: shares[0].y.clone(), + }; let secret = sharks.recover(&shares); assert!(secret.is_err()); } @@ -107,7 +123,7 @@ mod tests { fn test_integration_works() { let sharks = Sharks(255); let dealer = sharks.dealer(&[1, 2, 3, 4]); - let shares: std::collections::HashMap> = dealer.take(255).collect(); + let shares: Vec = dealer.take(255).collect(); let secret = sharks.recover(&shares).unwrap(); assert_eq!(secret, vec![1, 2, 3, 4]); } diff --git a/src/math.rs b/src/math.rs index 10be8b7..a760cb0 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,29 +1,26 @@ // A module which contains necessary algorithms to compute Shamir's shares and recover secrets -use std::collections::HashMap; - use rand::distributions::{Distribution, Uniform}; use super::field::GF256; +use super::share::Share; // Finds the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach). // The expected `shares` argument format is the same as the output by the `get_evaluatorĀ“ function. // Where each (key, value) pair corresponds to one share, where the key is the `x` and the value is a vector of `y`, // where each element corresponds to one of the secret's byte chunks. -pub fn interpolate(shares: &HashMap>) -> Vec { - let n_chunks = shares.values().take(1).collect::>>()[0].len(); - - (0..n_chunks) +pub fn interpolate(shares: &[Share]) -> Vec { + (0..shares[0].y.len()) .map(|s| { shares .iter() - .map(|(x_i, y_i)| { + .map(|s_i| { shares - .keys() - .filter(|x_j| *x_j != x_i) - .map(|x_j| *x_j / (*x_j - *x_i)) + .iter() + .filter(|s_j| s_j.x != s_i.x) + .map(|s_j| s_j.x / (s_j.x - s_i.x)) .product::() - * y_i[s] + * s_i.y[s] }) .sum::() .0 @@ -51,21 +48,21 @@ pub fn random_polynomial(s: GF256, k: u8) -> Vec { // Each item of the iterator is a tuple `(x, [f_1(x), f_2(x)..])` where eaxh `f_i` is the result for the ith polynomial. // Each polynomial corresponds to one byte chunk of the original secret. // The iterator will start at `x = 1` and end at `x = 255`. -pub fn get_evaluator(polys: Vec>) -> impl Iterator)> { +pub fn get_evaluator(polys: Vec>) -> impl Iterator { (1..=u8::max_value()).map(GF256).map(move |x| { - ( + (Share { x, - polys + y: polys .iter() .map(|p| p.iter().fold(GF256(0), |acc, c| acc * x + *c)) .collect(), - ) + }) }) } #[cfg(test)] mod tests { - use super::{get_evaluator, interpolate, random_polynomial, GF256}; + use super::{get_evaluator, interpolate, random_polynomial, Share, GF256}; #[test] fn random_polynomial_works() { @@ -77,7 +74,7 @@ mod tests { #[test] fn evaluator_works() { let iter = get_evaluator(vec![vec![GF256(3), GF256(2), GF256(5)]]); - let values: Vec<_> = iter.take(2).collect(); + let values: Vec<_> = iter.take(2).map(|s| (s.x, s.y)).collect(); assert_eq!( values, vec![(GF256(1), vec![GF256(4)]), (GF256(2), vec![GF256(13)])] @@ -88,7 +85,7 @@ mod tests { fn interpolate_works() { let poly = random_polynomial(GF256(185), 10); let iter = get_evaluator(vec![poly]); - let shares = iter.take(10).collect(); + let shares: Vec = iter.take(10).collect(); let root = interpolate(&shares); assert_eq!(root, vec![185]); } diff --git a/src/share.rs b/src/share.rs new file mode 100644 index 0000000..c7cd53c --- /dev/null +++ b/src/share.rs @@ -0,0 +1,69 @@ +use super::field::GF256; + +/// A share used to reconstruct the secret. Can be serialized to and from a byte array. +/// +/// Usage example: +/// ``` +/// use sharks::{Sharks, Share}; +/// # fn send_to_printer(_: Vec) {} +/// # fn ask_shares() -> Vec> {vec![vec![1, 2], vec![2, 3], vec![3, 4]]} +/// +/// // Transmit the share bytes to a printer +/// let sharks = Sharks(3); +/// let dealer = sharks.dealer(&[1, 2, 3]); +/// +/// // Get 5 shares and print paper keys +/// for s in dealer.take(5) { +/// send_to_printer(Vec::from(&s)); +/// }; +/// +/// // Get share bytes from an external source and recover secret +/// let shares_bytes: Vec> = ask_shares(); +/// let shares: Vec = shares_bytes.iter().map(|s| Share::from(s.as_slice())).collect(); +/// let secret = sharks.recover(&shares).unwrap(); +pub struct Share { + pub x: GF256, + pub y: Vec, +} + +/// Obtains a byte vector from a `Share` instance +impl From<&Share> for Vec { + fn from(s: &Share) -> Vec { + let mut bytes = Vec::with_capacity(s.y.len() + 1); + bytes.push(s.x.0); + bytes.extend(s.y.iter().map(|p| p.0)); + bytes + } +} + +/// Obtains a `Share` instance from a byte slice +impl From<&[u8]> for Share { + fn from(s: &[u8]) -> Share { + let x = GF256(s[0]); + let y = s[1..].iter().map(|p| GF256(*p)).collect(); + Share { x, y } + } +} + +#[cfg(test)] +mod tests { + use super::{Share, GF256}; + + #[test] + fn vec_from_share_works() { + let share = Share { + x: GF256(1), + y: vec![GF256(2), GF256(3)], + }; + let bytes = Vec::from(&share); + assert_eq!(bytes, vec![1, 2, 3]); + } + + #[test] + fn share_from_u8_slice_works() { + let bytes = [1, 2, 3]; + let share = Share::from(&bytes[..]); + assert_eq!(share.x, GF256(1)); + assert_eq!(share.y, vec![GF256(2), GF256(3)]); + } +}