changed arithmetic to GF256, unlimited secret length, updated docs (#1)

This commit is contained in:
Aitor Ruano 2020-01-21 10:09:20 +01:00 committed by GitHub
parent a3e43f373a
commit 5344dda94b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 390 additions and 216 deletions

View File

@ -4,6 +4,18 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.0] - 2020-01-21
### Added
- Computations performed over GF256 (much faster)
- Secret can now be arbitrarily long
### Changed
- Some method names and docs
- Maximum number of shares enforced by Rust static types instead of conditional branching
### Removed
- Modular arithmetic around Mersenne primes
## [0.1.1] - 2020-01-13 ## [0.1.1] - 2020-01-13
### Fixed ### Fixed
- Typo in cargo description - Typo in cargo description

View File

@ -1,6 +1,6 @@
[package] [package]
name = "sharks" name = "sharks"
version = "0.1.1" version = "0.2.0"
authors = ["Aitor Ruano <codearm@pm.me>"] authors = ["Aitor Ruano <codearm@pm.me>"]
description = "Fast, small and secure Shamir's Secret Sharing library crate" description = "Fast, small and secure Shamir's Secret Sharing library crate"
homepage = "https://github.com/c0dearm/sharks" homepage = "https://github.com/c0dearm/sharks"
@ -18,8 +18,6 @@ maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
rand = "0.7" rand = "0.7"
num-bigint = "0.2"
num-traits = "0.2"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View File

@ -15,7 +15,7 @@ Add this to your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
sharks = "0.1" sharks = "0.2"
``` ```
To get started using Sharks, see the [Rust docs](https://docs.rs/sharks) To get started using Sharks, see the [Rust docs](https://docs.rs/sharks)
@ -34,12 +34,9 @@ like generating more shares than what's allowed by the finite field length.
## Limitations ## Limitations
Currently only finite fields with modulus up to 128 bits (12th Mersenne prime) are supported. This means: Because the Galois finite field it uses is [GF256](https://en.wikipedia.org/wiki/Finite_field#GF(p2)_for_an_odd_prime_p),
- Only up to `2^128` shares can be generated. only up to 255 shares can be generated for a given secret. A larger number would be insecure as shares would start duplicating.
- Maximum secret length is 128 bits. Nevertheless, the secret can be arbitrarily long as computations are performed on single byte chunks.
This is imposed by the Rust maximum unsigned integer length, which is `u128`.
Going around this limitation would mean using crates like `num-bigint` in most of the computations, reducing performance drastically.
## Testing ## Testing
@ -48,9 +45,9 @@ You can run them with `cargo test` and `cargo bench`.
### Benchmark results [min mean max] ### Benchmark results [min mean max]
| CPU | obtain_shares_iterator | step_shares_iterator | recover_secret | | CPU | obtain_shares_dealer | step_shares_dealer | recover_secret |
| ----------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | | ----------------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- |
| Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz | [14.023 us 14.087 us 14.146 us] | [413.19 us 414.90 us 416.60 us] | [24.978 ms 25.094 ms 25.226 ms] | | 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] |
# Contributing # Contributing

View File

@ -1,26 +1,25 @@
use std::collections::HashMap;
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use sharks::SecretShares;
fn secret_shares_generation(c: &mut Criterion) { use sharks::Sharks;
let shamir = SecretShares::new(1000, 128).unwrap();
let mut iter = shamir.iter_shares(12345).unwrap();
c.bench_function("obtain_shares_iterator", |b| { fn dealer(c: &mut Criterion) {
b.iter(|| shamir.iter_shares(black_box(12345))) let sharks = Sharks(255);
let mut dealer = sharks.dealer(&[1]);
c.bench_function("obtain_shares_dealer", |b| {
b.iter(|| sharks.dealer(black_box(&[1])))
}); });
c.bench_function("step_shares_iterator", |b| b.iter(|| iter.next())); c.bench_function("step_shares_dealer", |b| b.iter(|| dealer.next()));
} }
fn secret_from_shares(c: &mut Criterion) { fn recover(c: &mut Criterion) {
let shamir = SecretShares::new(10, 128).unwrap(); let sharks = Sharks(255);
let shares: HashMap<u128, u128> = shamir.iter_shares(12345).unwrap().take(100).collect(); let shares = sharks.dealer(&[1]).take(255).collect();
c.bench_function("recover_secret", |b| { c.bench_function("recover_secret", |b| {
b.iter(|| shamir.secret_from(black_box(&shares))) b.iter(|| sharks.recover(black_box(&shares)))
}); });
} }
criterion_group!(benches, secret_shares_generation, secret_from_shares); criterion_group!(benches, dealer, recover);
criterion_main!(benches); criterion_main!(benches);

218
src/field.rs Normal file
View File

@ -0,0 +1,218 @@
// Basic operations overrided for the Galois Field 256 (2**8)
// Uses pre-calculated tables for 0x11d primitive polynomial (x**8 + x**4 + x**3 + x**2 + 1)
use std::iter::{Product, Sum};
use std::ops::{Add, Div, Mul, Sub};
const LOG_TABLE: [u8; 256] = [
0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf,
];
const EXP_TABLE: [u8; 512] = [
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01,
0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,
0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x9d,
0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,
0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,
0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,
0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,
0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,
0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,
0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,
0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,
0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,
0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,
0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,
0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 0x12,
0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 0x2c,
0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01, 0x02,
];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct GF256(pub u8);
#[allow(clippy::suspicious_arithmetic_impl)]
impl Add for GF256 {
type Output = GF256;
fn add(self, other: Self) -> Self::Output {
Self(self.0 ^ other.0)
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Sub for GF256 {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Self(self.0 ^ other.0)
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Mul for GF256 {
type Output = Self;
fn mul(self, other: Self) -> Self::Output {
let log_x = LOG_TABLE[self.0 as usize] as usize;
let log_y = LOG_TABLE[other.0 as usize] as usize;
if self.0 == 0 || other.0 == 0 {
Self(0)
} else {
Self(EXP_TABLE[log_x + log_y])
}
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Div for GF256 {
type Output = Self;
fn div(self, other: Self) -> Self::Output {
let log_x = LOG_TABLE[self.0 as usize] as usize;
let log_y = LOG_TABLE[other.0 as usize] as usize;
if self.0 == 0 {
Self(0)
} else {
Self(EXP_TABLE[log_x + 255 - log_y])
}
}
}
impl Sum for GF256 {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self(0), |acc, x| acc + x)
}
}
impl Product for GF256 {
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self(1), |acc, x| acc * x)
}
}
#[cfg(test)]
mod tests {
use super::{EXP_TABLE, GF256, LOG_TABLE};
#[test]
fn add_works() {
let answers: [u8; 256] = [
1, 2, 5, 17, 18, 18, 90, 70, 30, 229, 71, 6, 214, 239, 212, 109, 72, 252, 205, 84, 128,
248, 5, 72, 147, 194, 111, 244, 208, 56, 44, 177, 152, 173, 43, 179, 196, 110, 155, 20,
95, 71, 59, 173, 30, 211, 29, 102, 91, 57, 199, 119, 126, 15, 169, 25, 148, 32, 96,
170, 244, 139, 172, 7, 89, 1, 234, 160, 255, 242, 110, 65, 135, 82, 172, 188, 14, 173,
90, 120, 203, 55, 71, 117, 228, 64, 106, 194, 15, 51, 204, 255, 216, 142, 55, 162, 199,
237, 245, 37, 210, 106, 58, 230, 102, 32, 28, 60, 42, 56, 221, 243, 75, 65, 165, 227,
242, 248, 190, 184, 117, 162, 9, 105, 228, 192, 193, 155, 130, 103, 238, 171, 52, 237,
185, 164, 40, 212, 255, 175, 181, 208, 212, 76, 75, 232, 3, 94, 116, 28, 225, 214, 88,
214, 171, 171, 199, 245, 62, 93, 209, 238, 110, 56, 83, 45, 240, 179, 108, 98, 64, 1,
167, 10, 79, 158, 17, 141, 120, 224, 130, 27, 63, 90, 17, 11, 87, 143, 226, 58, 239,
227, 157, 52, 113, 188, 127, 246, 163, 120, 216, 47, 57, 12, 162, 171, 60, 80, 61, 3,
98, 224, 80, 111, 172, 69, 56, 251, 173, 231, 23, 137, 180, 83, 217, 125, 23, 32, 161,
211, 84, 164, 252, 6, 237, 0, 177, 254, 39, 193, 99, 246, 101, 148, 28, 14, 98, 107,
111, 224, 152, 50, 5, 23, 214, 174,
];
for (i, a) in answers.iter().enumerate() {
assert_eq!((GF256(LOG_TABLE[i]) + GF256(EXP_TABLE[i])).0, *a);
}
}
#[test]
fn sub_works() {
add_works();
}
#[test]
fn mul_works() {
let answers: [u8; 256] = [
0, 0, 4, 200, 32, 14, 206, 179, 39, 134, 169, 160, 32, 59, 184, 50, 45, 121, 69, 43,
102, 43, 139, 169, 18, 94, 107, 84, 18, 157, 159, 51, 211, 1, 52, 13, 51, 128, 31, 219,
240, 230, 212, 219, 197, 19, 11, 135, 93, 163, 237, 53, 91, 177, 135, 124, 240, 224, 6,
158, 167, 155, 155, 38, 223, 144, 70, 54, 50, 45, 134, 170, 126, 223, 103, 207, 253,
176, 75, 98, 137, 87, 59, 50, 208, 116, 29, 200, 128, 82, 13, 138, 107, 53, 42, 34,
123, 203, 65, 174, 111, 101, 19, 78, 165, 62, 115, 108, 175, 139, 126, 107, 55, 196,
30, 209, 126, 8, 15, 211, 57, 191, 37, 254, 24, 136, 30, 111, 188, 30, 209, 208, 49,
132, 181, 22, 207, 241, 28, 2, 97, 58, 244, 179, 190, 120, 249, 174, 99, 6, 215, 232,
173, 1, 20, 216, 224, 191, 247, 78, 223, 101, 153, 1, 182, 203, 213, 75, 132, 98, 53,
204, 13, 177, 22, 88, 218, 21, 32, 68, 247, 153, 11, 190, 47, 128, 214, 33, 110, 194,
102, 77, 5, 178, 74, 65, 134, 62, 91, 190, 133, 15, 134, 94, 37, 247, 205, 51, 224,
152, 15, 13, 13, 233, 189, 206, 100, 131, 222, 5, 70, 182, 231, 176, 167, 150, 156,
249, 29, 189, 96, 149, 239, 162, 43, 239, 89, 8, 9, 57, 118, 227, 168, 243, 164, 188,
125, 8, 8, 240, 36, 45, 21, 20, 44, 175,
];
for (i, a) in answers.iter().enumerate() {
assert_eq!((GF256(LOG_TABLE[i]) * GF256(EXP_TABLE[i])).0, *a);
}
}
#[test]
fn div_works() {
let answers: [u8; 256] = [
0, 0, 71, 174, 173, 87, 134, 213, 152, 231, 124, 39, 203, 113, 13, 198, 88, 171, 55,
150, 177, 227, 25, 225, 227, 180, 157, 225, 252, 122, 88, 161, 45, 87, 148, 78, 40,
165, 74, 134, 142, 120, 121, 163, 156, 75, 154, 241, 239, 27, 152, 130, 125, 235, 230,
32, 138, 225, 145, 90, 214, 226, 182, 168, 155, 175, 179, 124, 105, 169, 249, 58, 201,
14, 155, 217, 196, 254, 201, 143, 229, 12, 178, 24, 100, 226, 163, 234, 177, 36, 75,
106, 114, 208, 162, 63, 235, 181, 108, 131, 248, 51, 190, 187, 235, 115, 112, 37, 79,
90, 112, 237, 195, 121, 136, 110, 174, 143, 113, 134, 229, 255, 35, 175, 156, 208, 240,
222, 94, 202, 228, 34, 123, 23, 48, 18, 122, 114, 75, 243, 212, 139, 56, 132, 157, 119,
219, 170, 236, 11, 51, 86, 224, 221, 142, 200, 154, 136, 179, 72, 3, 32, 142, 149, 180,
209, 253, 17, 210, 134, 162, 106, 38, 108, 154, 154, 74, 181, 115, 142, 204, 195, 23,
162, 178, 41, 9, 90, 190, 14, 2, 45, 227, 253, 115, 93, 155, 244, 83, 219, 11, 196,
167, 241, 33, 60, 103, 69, 181, 189, 145, 130, 174, 137, 65, 65, 45, 153, 79, 236, 199,
209, 41, 10, 205, 44, 182, 38, 222, 209, 253, 247, 64, 71, 32, 1, 27, 53, 4, 110, 170,
221, 215, 4, 179, 163, 64, 90, 152, 163, 235, 6, 41, 93, 176, 175,
];
for (i, a) in answers.iter().enumerate() {
assert_eq!((GF256(LOG_TABLE[i]) / GF256(EXP_TABLE[i])).0, *a);
}
}
#[test]
fn sum_works() {
let values = vec![GF256(0x53), GF256(0xCA), GF256(0)];
assert_eq!(values.into_iter().sum::<GF256>().0, 0x99);
}
#[test]
fn product_works() {
let values = vec![GF256(1), GF256(1), GF256(4)];
assert_eq!(values.into_iter().product::<GF256>().0, 4);
}
}

View File

@ -1,130 +1,114 @@
//! Fast, small and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library crate //! Fast, small and secure [Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) library crate
//! //!
//! # Usage example //! Usage example:
//! ``` //! ```
//! // Configure algorithm with minimum 3 shares to recover secret and security level 12 //! use sharks::Sharks;
//! let shamir = sharks::SecretShares::new(3, 12).unwrap(); //!
//! // Generate 3 shares for the 12345 secret //! // Set a minimum threshold of 10 shares
//! let shares = shamir.iter_shares(12345).unwrap().take(3).collect(); //! let sharks = Sharks(10);
//! // Recover the secret from the shares //! // Obtain an iterator over the shares for secret [1, 2, 3, 4]
//! let secret = shamir.secret_from(&shares).unwrap(); //! let dealer = sharks.dealer(&[1, 2, 3, 4]);
//! assert_eq!(secret, 12345); //! // Get 10 shares
//! let shares = dealer.take(10).collect();
//! // Recover the original secret!
//! let secret = sharks.recover(&shares).unwrap();
//! assert_eq!(secret, vec![1, 2, 3, 4]);
//! ``` //! ```
mod field;
mod math;
use std::collections::HashMap; use std::collections::HashMap;
mod math; use field::GF256;
mod mersenne;
/// Generate new [Shamir's secret shares](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) or recover secrets from them. /// Tuple struct which implements methods to generate shares and recover secrets over a 256 bits Galois Field.
pub struct SecretShares { /// Its only parameter is the minimum shares threshold.
min_shares: usize, ///
prime: u128, /// Usage example:
} /// ```
/// # use sharks::Sharks;
/// // 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();
/// // Recover the original secret!
/// let secret = sharks.recover(&shares).unwrap();
/// assert_eq!(secret, vec![1, 2, 3, 4]);
/// ```
pub struct Sharks(pub u8);
impl SecretShares { impl Sharks {
/// Returns a result containing a`SecretShares` instance if parameters are reasonable. /// Given a `secret` byte slice, returns an `Iterator` along new shares.
/// The maximum number of shares that can be generated is 256.
/// ///
/// `security_level` is the index of the [Mersenne prime](https://en.wikipedia.org/wiki/Mersenne_prime) to use as the finite field prime modulo (the higher the more secure, but slower). /// Example:
/// Currently, only up to 12 is supported (`p=127, Mp = 2^127 - 1`).
///
/// If `min_shares` is larger or equal to the Mersenne prime an error is returned, as this configuration would generate insecure shares.
///
/// Example, create an instance with minimum 3 shares to recover a secret and 128 bits of security:
/// ```
/// let shamir = sharks::SecretShares::new(3, 12);
/// assert!(shamir.is_ok());
/// ```
pub fn new(min_shares: usize, security_level: usize) -> Result<Self, &'static str> {
let security_level = std::cmp::min(security_level - 1, mersenne::EXPONENTS.len() - 1);
let prime = u128::pow(2, mersenne::EXPONENTS[security_level]) - 1;
if (min_shares as u128) < prime {
Ok(SecretShares { min_shares, prime })
} else {
Err("Minimum shares for recovery is too large for current security level")
}
}
/// Given a `secret` returns a result with an iterator which generates shares `(x, f(x))` for x from [1, p).
///
/// If `secret` is larger or equal than the Mersenne prime an error is returned, as it would be irrecoverable.
///
/// Example, generate 10 shares for secret `12345`:
/// ``` /// ```
/// # use std::collections::HashMap; /// # use std::collections::HashMap;
/// let shamir = sharks::SecretShares::new(3, 12).unwrap(); /// # use sharks::Sharks;
/// let shares: HashMap<u128, u128> = shamir.iter_shares(12345).unwrap().take(10).collect(); /// # let sharks = Sharks(3);
/// ``` /// // Obtain an iterator over the shares for secret [1, 2]
pub fn iter_shares(&self, secret: u128) -> Result<impl Iterator<Item = (u128, u128)>, &str> { /// let dealer = sharks.dealer(&[1, 2]);
if secret < self.prime { /// // Get 3 shares
let (p, coeffs) = math::compute_coeffs(secret, self.min_shares, self.prime); /// let shares: HashMap<_, _> = dealer.take(3).collect();
Ok(math::get_evaluator(coeffs, p)) pub fn dealer(&self, secret: &[u8]) -> impl Iterator<Item = (GF256, Vec<GF256>)> {
} else { let mut polys = Vec::with_capacity(secret.len());
Err("Secret is too large for current security level")
for chunk in secret {
polys.push(math::random_polynomial(GF256(*chunk), self.0))
} }
math::get_evaluator(polys)
} }
/// Given a set of distinct `shares`, returns a result with the recovered secret. /// Given a `HashMap` of shares, recovers the original secret.
/// If the number of shares is less than the minimum threshold an `Err` is returned,
/// otherwise an `Ok` containing the secret.
/// ///
/// If the number of `shares` is less than the number of minimum shares an error is returned as the secret is irrecoverable. /// Example:
///
/// Example, recover the `12345` secret:
/// ``` /// ```
/// let shamir = sharks::SecretShares::new(3, 12).unwrap(); /// # use sharks::Sharks;
/// let shares = shamir.iter_shares(12345).unwrap().take(3).collect(); /// # let sharks = Sharks(3);
/// let secret = shamir.secret_from(&shares).unwrap(); /// # let mut shares = sharks.dealer(&[1]).take(3).collect();
/// assert_eq!(secret, 12345); /// // Revover original secret from shares
/// ``` /// let mut secret = sharks.recover(&shares);
pub fn secret_from(&self, shares: &HashMap<u128, u128>) -> Result<u128, &str> { /// // Secret correctly recovered
if shares.len() < self.min_shares { /// assert!(secret.is_ok());
Err("Not enough shares to recover secret") /// // Remove shares for demonstrastion 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 {
Err("Not enough shares to recover original secret")
} else { } else {
Ok(math::lagrange_root(shares, self.prime)) Ok(math::interpolate(shares))
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::SecretShares; use super::{Sharks, GF256};
#[test] #[test]
fn test_security_level_range() { fn test_insufficient_shares_err() {
let shamir = SecretShares::new(10, 1000).unwrap(); let sharks = Sharks(255);
assert_eq!(shamir.prime, u128::pow(2, 127) - 1); let dealer = sharks.dealer(&[1]);
let shares = dealer.take(254).collect();
let shamir = SecretShares::new(2, 1).unwrap(); let secret = sharks.recover(&shares);
assert_eq!(shamir.prime, 3);
}
#[test]
fn test_min_shares_too_large() {
let shamir = SecretShares::new(3, 1);
assert!(shamir.is_err());
}
#[test]
fn test_secret_too_large() {
let shamir = SecretShares::new(2, 1).unwrap();
let shares = shamir.iter_shares(3);
assert!(shares.is_err());
}
#[test]
fn test_insufficient_shares() {
let shamir = SecretShares::new(2, 1).unwrap();
let shares = shamir.iter_shares(2).unwrap().take(1).collect();
let secret = shamir.secret_from(&shares);
assert!(secret.is_err()); assert!(secret.is_err());
} }
#[test] #[test]
fn test_integration() { fn test_integration_works() {
let shamir = SecretShares::new(10, 128).unwrap(); let sharks = Sharks(255);
let shares = shamir.iter_shares(12345).unwrap().take(100).collect(); let dealer = sharks.dealer(&[1, 2, 3, 4]);
let secret = shamir.secret_from(&shares).unwrap(); let shares: std::collections::HashMap<GF256, Vec<GF256>> = dealer.take(255).collect();
assert_eq!(secret, 12345); let secret = sharks.recover(&shares).unwrap();
assert_eq!(secret, vec![1, 2, 3, 4]);
} }
} }

View File

@ -1,126 +1,95 @@
// A module that contains necessary algorithms to compute Shamir's shares and recover secrets // A module which contains necessary algorithms to compute Shamir's shares and recover secrets
use std::collections::HashMap; use std::collections::HashMap;
use num_bigint::{BigInt, BigUint};
use num_traits::cast::ToPrimitive;
use num_traits::Zero;
use rand::distributions::{Distribution, Uniform}; use rand::distributions::{Distribution, Uniform};
// Computes `num/(num - b) mod p`, a necessary step to compute the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach). use super::field::GF256;
// To find the modulo multiplicative inverse of `num - b` the [Extended Euclidean Algorithm](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation) is used.
fn div_diff_mod(num: &u128, b: &u128, p: u128) -> u128 {
let (mut m, mut x, mut inv, mut den) = (
p,
0i128,
1i128,
if num < b { p - (b - num) } else { num - b },
);
while den > 1 {
inv -= ((den / m) as i128) * x;
den %= m;
std::mem::swap(&mut den, &mut m);
std::mem::swap(&mut x, &mut inv);
}
let mut res = BigInt::from(inv);
if inv < 0 {
res += p
}
(num * res % p).to_u128().unwrap()
}
// Finds the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach). // Finds the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach).
pub fn lagrange_root(points: &HashMap<u128, u128>, p: u128) -> u128 { // The expected `shares` argument format is the same as the output by the `get_evaluator´ function.
(points // Where each (key, value) pair corresponds to one share, where the key is the `x` and the value is a vector of `y`,
.iter() // where each element corresponds to one of the secret's byte chunks.
.enumerate() pub fn interpolate(shares: &HashMap<GF256, Vec<GF256>>) -> Vec<u8> {
.map(|(j, (xj, yj))| { let n_chunks = shares.values().take(1).collect::<Vec<&Vec<GF256>>>()[0].len();
points
(0..n_chunks)
.map(|s| {
shares
.iter() .iter()
.enumerate() .map(|(x_i, y_i)| {
.filter(|(m, _)| *m != j) shares
.map(|(_, (xm, _))| div_diff_mod(xm, xj, p)) .keys()
.product::<BigUint>() .filter(|x_j| *x_j != x_i)
* yj .map(|x_j| *x_j / (*x_j - *x_i))
% p .product::<GF256>()
* y_i[s]
})
.sum::<GF256>()
.0
}) })
.sum::<BigUint>() .collect()
% p)
.to_u128()
.unwrap()
} }
// Generates `k` polynomial coefficients, being the last one `s` and the others randomly generated between `[1, p)`. // Generates `k` polynomial coefficients, being the last one `s` and the others randomly generated between `[1, 255]`.
// Coefficient degrees go from higher to lower in the returned vector order. // Coefficient degrees go from higher to lower in the returned vector order.
pub fn compute_coeffs(s: u128, k: usize, p: u128) -> (u128, Vec<u128>) { pub fn random_polynomial(s: GF256, k: u8) -> Vec<GF256> {
let mut coeffs = Vec::with_capacity(k); let k = k as usize;
let between = Uniform::new(1, p); let mut poly = Vec::with_capacity(k);
let between = Uniform::new_inclusive(1, 255);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
for _ in 1..k { for _ in 1..k {
coeffs.push(between.sample(&mut rng)); poly.push(GF256(between.sample(&mut rng)));
} }
coeffs.push(s); poly.push(s);
(p, coeffs) poly
} }
// Given a set of polynomial coefficients `coeffs` and a modulus `p`, returns an iterator that computes a `(x, f(x) mod p)` point // Returns an iterator over the points of the `polys` polynomials passed as argument.
// on each iteration. The iterator starts for `x = 1` and ends at `x = p-1`. // 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.
pub fn get_evaluator(coeffs: Vec<u128>, p: u128) -> impl Iterator<Item = (u128, u128)> { // Each polynomial corresponds to one byte chunk of the original secret.
(1..p).map(move |x| { // 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>)> {
(1..=u8::max_value()).map(GF256).map(move |x| {
( (
x, x,
coeffs polys
.iter() .iter()
.fold(BigUint::zero(), |acc, c| (acc * x + c) % p) .map(|p| p.iter().fold(GF256(0), |acc, c| acc * x + *c))
.to_u128() .collect(),
.unwrap(),
) )
}) })
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{compute_coeffs, div_diff_mod, get_evaluator, lagrange_root}; use super::{get_evaluator, interpolate, random_polynomial, GF256};
#[test] #[test]
fn div_diff_mod_works() { fn random_polynomial_works() {
let res = div_diff_mod(&2, &1, 7); let poly = random_polynomial(GF256(1), 3);
assert_eq!(res, 2); assert_eq!(poly.len(), 3);
assert_eq!(poly[2], GF256(1));
let res = div_diff_mod(&1, &2, 7);
assert_eq!(res, 6);
}
#[test]
fn lagrange_root_works() {
let iter = get_evaluator(vec![3, 2, 1], 7);
let values = iter.take(3).collect();
let root = lagrange_root(&values, 7);
assert_eq!(root, 1);
let iter = get_evaluator(vec![3, 2, 5], 7);
let values = iter.take(3).collect();
let root = lagrange_root(&values, 7);
assert_eq!(root, 5);
}
#[test]
fn compute_coeffs_works() {
let coeffs = compute_coeffs(1, 4, 7);
assert_eq!(coeffs.0, 7);
assert_eq!(coeffs.1.len(), 4);
assert_eq!(coeffs.1[3], 1);
} }
#[test] #[test]
fn evaluator_works() { fn evaluator_works() {
let iter = get_evaluator(vec![3, 2, 5], 7); 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).collect();
assert_eq!(values, vec![(1, 3), (2, 0)]); assert_eq!(
values,
vec![(GF256(1), vec![GF256(4)]), (GF256(2), vec![GF256(13)])]
);
}
#[test]
fn interpolate_works() {
let poly = random_polynomial(GF256(185), 10);
let iter = get_evaluator(vec![poly]);
let shares = iter.take(10).collect();
let root = interpolate(&shares);
assert_eq!(root, vec![185]);
} }
} }

View File

@ -1,3 +0,0 @@
// A table containing the exponents of found [Mersenne primes](https://en.wikipedia.org/wiki/Mersenne_prime)
// To be used as finite field modulo for shares and secret recovery computation
pub const EXPONENTS: [u32; 12] = [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127];