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/),
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
### Fixed
- Typo in cargo description

View File

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

View File

@ -15,7 +15,7 @@ Add this to your `Cargo.toml`:
```toml
[dependencies]
sharks = "0.1"
sharks = "0.2"
```
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
Currently only finite fields with modulus up to 128 bits (12th Mersenne prime) are supported. This means:
- Only up to `2^128` shares can be generated.
- Maximum secret length is 128 bits.
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.
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 255 shares can be generated for a given secret. A larger number would be insecure as shares would start duplicating.
Nevertheless, the secret can be arbitrarily long as computations are performed on single byte chunks.
## Testing
@ -48,9 +45,9 @@ You can run them with `cargo test` and `cargo bench`.
### 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

View File

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

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
//!
//! # Usage example
//! Usage example:
//! ```
//! // Configure algorithm with minimum 3 shares to recover secret and security level 12
//! let shamir = sharks::SecretShares::new(3, 12).unwrap();
//! // Generate 3 shares for the 12345 secret
//! let shares = shamir.iter_shares(12345).unwrap().take(3).collect();
//! // Recover the secret from the shares
//! let secret = shamir.secret_from(&shares).unwrap();
//! assert_eq!(secret, 12345);
//! 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]);
//! ```
mod field;
mod math;
use std::collections::HashMap;
mod math;
mod mersenne;
use field::GF256;
/// Generate new [Shamir's secret shares](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing) or recover secrets from them.
pub struct SecretShares {
min_shares: usize,
prime: u128,
}
/// 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;
/// // 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 {
/// Returns a result containing a`SecretShares` instance if parameters are reasonable.
impl Sharks {
/// 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).
/// 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`:
/// Example:
/// ```
/// # use std::collections::HashMap;
/// let shamir = sharks::SecretShares::new(3, 12).unwrap();
/// let shares: HashMap<u128, u128> = shamir.iter_shares(12345).unwrap().take(10).collect();
/// ```
pub fn iter_shares(&self, secret: u128) -> Result<impl Iterator<Item = (u128, u128)>, &str> {
if secret < self.prime {
let (p, coeffs) = math::compute_coeffs(secret, self.min_shares, self.prime);
Ok(math::get_evaluator(coeffs, p))
} else {
Err("Secret is too large for current security level")
}
/// # use sharks::Sharks;
/// # 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 mut polys = Vec::with_capacity(secret.len());
for chunk in secret {
polys.push(math::random_polynomial(GF256(*chunk), self.0))
}
/// Given a set of distinct `shares`, returns a result with the recovered secret.
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,
/// 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, recover the `12345` secret:
/// Example:
/// ```
/// let shamir = sharks::SecretShares::new(3, 12).unwrap();
/// let shares = shamir.iter_shares(12345).unwrap().take(3).collect();
/// let secret = shamir.secret_from(&shares).unwrap();
/// assert_eq!(secret, 12345);
/// ```
pub fn secret_from(&self, shares: &HashMap<u128, u128>) -> Result<u128, &str> {
if shares.len() < self.min_shares {
Err("Not enough shares to recover secret")
/// # use sharks::Sharks;
/// # let sharks = Sharks(3);
/// # let mut shares = sharks.dealer(&[1]).take(3).collect();
/// // Revover original secret from shares
/// let mut secret = sharks.recover(&shares);
/// // Secret correctly recovered
/// assert!(secret.is_ok());
/// // 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 {
Ok(math::lagrange_root(shares, self.prime))
Ok(math::interpolate(shares))
}
}
}
#[cfg(test)]
mod tests {
use super::SecretShares;
use super::{Sharks, GF256};
#[test]
fn test_security_level_range() {
let shamir = SecretShares::new(10, 1000).unwrap();
assert_eq!(shamir.prime, u128::pow(2, 127) - 1);
let shamir = SecretShares::new(2, 1).unwrap();
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);
fn test_insufficient_shares_err() {
let sharks = Sharks(255);
let dealer = sharks.dealer(&[1]);
let shares = dealer.take(254).collect();
let secret = sharks.recover(&shares);
assert!(secret.is_err());
}
#[test]
fn test_integration() {
let shamir = SecretShares::new(10, 128).unwrap();
let shares = shamir.iter_shares(12345).unwrap().take(100).collect();
let secret = shamir.secret_from(&shares).unwrap();
assert_eq!(secret, 12345);
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 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 num_bigint::{BigInt, BigUint};
use num_traits::cast::ToPrimitive;
use num_traits::Zero;
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).
// 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()
}
use super::field::GF256;
// 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 {
(points
// 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)
.map(|s| {
shares
.iter()
.enumerate()
.map(|(j, (xj, yj))| {
points
.iter()
.enumerate()
.filter(|(m, _)| *m != j)
.map(|(_, (xm, _))| div_diff_mod(xm, xj, p))
.product::<BigUint>()
* yj
% p
.map(|(x_i, y_i)| {
shares
.keys()
.filter(|x_j| *x_j != x_i)
.map(|x_j| *x_j / (*x_j - *x_i))
.product::<GF256>()
* y_i[s]
})
.sum::<BigUint>()
% p)
.to_u128()
.unwrap()
.sum::<GF256>()
.0
})
.collect()
}
// 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.
pub fn compute_coeffs(s: u128, k: usize, p: u128) -> (u128, Vec<u128>) {
let mut coeffs = Vec::with_capacity(k);
let between = Uniform::new(1, p);
pub fn random_polynomial(s: GF256, k: u8) -> Vec<GF256> {
let k = k as usize;
let mut poly = Vec::with_capacity(k);
let between = Uniform::new_inclusive(1, 255);
let mut rng = rand::thread_rng();
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
// on each iteration. The iterator starts for `x = 1` and ends at `x = p-1`.
pub fn get_evaluator(coeffs: Vec<u128>, p: u128) -> impl Iterator<Item = (u128, u128)> {
(1..p).map(move |x| {
// Returns an iterator over the points of the `polys` polynomials passed as argument.
// 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>)> {
(1..=u8::max_value()).map(GF256).map(move |x| {
(
x,
coeffs
polys
.iter()
.fold(BigUint::zero(), |acc, c| (acc * x + c) % p)
.to_u128()
.unwrap(),
.map(|p| p.iter().fold(GF256(0), |acc, c| acc * x + *c))
.collect(),
)
})
}
#[cfg(test)]
mod tests {
use super::{compute_coeffs, div_diff_mod, get_evaluator, lagrange_root};
use super::{get_evaluator, interpolate, random_polynomial, GF256};
#[test]
fn div_diff_mod_works() {
let res = div_diff_mod(&2, &1, 7);
assert_eq!(res, 2);
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);
fn random_polynomial_works() {
let poly = random_polynomial(GF256(1), 3);
assert_eq!(poly.len(), 3);
assert_eq!(poly[2], GF256(1));
}
#[test]
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();
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];