Added Share Serialization and Created Share Struct (#2)

* Added share serialisation and created Share struct

Co-authored-by: Aitor Ruano <45633475+c0dearm@users.noreply.github.com>
This commit is contained in:
zer0x64 2020-01-22 09:01:35 -05:00 committed by Aitor Ruano
parent 5344dda94b
commit 3489574840
7 changed files with 151 additions and 48 deletions

View File

@ -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<GF256>) tuples
## [0.2.0] - 2020-01-21
### Added
- Computations performed over GF256 (much faster)

View File

@ -1,6 +1,6 @@
[package]
name = "sharks"
version = "0.2.0"
version = "0.3.0"
authors = ["Aitor Ruano <codearm@pm.me>"]
description = "Fast, small and secure Shamir's Secret Sharing library crate"
homepage = "https://github.com/c0dearm/sharks"

View File

@ -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

View File

@ -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<Share> = 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);

View File

@ -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<Share> = 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<Share> = 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<Item = (GF256, Vec<GF256>)> {
/// let shares: Vec<Share> = dealer.take(3).collect();
pub fn dealer(&self, secret: &[u8]) -> impl Iterator<Item = Share> {
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<Share> = 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<GF256, Vec<GF256>>) -> Result<Vec<u8>, &str> {
if shares.len() < self.0 as usize {
pub fn recover(&self, shares: &[Share]) -> Result<Vec<u8>, &str> {
let shares_x: HashSet<u8> = 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<Share> = 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<Share> = 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<GF256, Vec<GF256>> = dealer.take(255).collect();
let shares: Vec<Share> = dealer.take(255).collect();
let secret = sharks.recover(&shares).unwrap();
assert_eq!(secret, vec![1, 2, 3, 4]);
}

View File

@ -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<GF256, Vec<GF256>>) -> Vec<u8> {
let n_chunks = shares.values().take(1).collect::<Vec<&Vec<GF256>>>()[0].len();
(0..n_chunks)
pub fn interpolate(shares: &[Share]) -> Vec<u8> {
(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::<GF256>()
* y_i[s]
* s_i.y[s]
})
.sum::<GF256>()
.0
@ -51,21 +48,21 @@ pub fn random_polynomial(s: GF256, k: u8) -> Vec<GF256> {
// 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<Vec<GF256>>) -> impl Iterator<Item = (GF256, Vec<GF256>)> {
pub fn get_evaluator(polys: Vec<Vec<GF256>>) -> impl Iterator<Item = Share> {
(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<Share> = iter.take(10).collect();
let root = interpolate(&shares);
assert_eq!(root, vec![185]);
}

69
src/share.rs Normal file
View File

@ -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<u8>) {}
/// # fn ask_shares() -> Vec<Vec<u8>> {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<Vec<u8>> = ask_shares();
/// let shares: Vec<Share> = 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<GF256>,
}
/// Obtains a byte vector from a `Share` instance
impl From<&Share> for Vec<u8> {
fn from(s: &Share) -> Vec<u8> {
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)]);
}
}