diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5f332..f35bba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 3b35359..0261c2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sharks" -version = "0.1.1" +version = "0.2.0" authors = ["Aitor Ruano "] 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" diff --git a/README.md b/README.md index 864586c..8caa3c1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 5416f44..c82a426 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -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 = 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); diff --git a/src/field.rs b/src/field.rs new file mode 100644 index 0000000..95f87b3 --- /dev/null +++ b/src/field.rs @@ -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>(iter: I) -> Self { + iter.fold(Self(0), |acc, x| acc + x) + } +} + +impl Product for GF256 { + fn product>(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::().0, 0x99); + } + + #[test] + fn product_works() { + let values = vec![GF256(1), GF256(1), GF256(4)]; + assert_eq!(values.into_iter().product::().0, 4); + } +} diff --git a/src/lib.rs b/src/lib.rs index f521b3b..79b2b80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { - 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 = shamir.iter_shares(12345).unwrap().take(10).collect(); - /// ``` - pub fn iter_shares(&self, secret: u128) -> Result, &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)> { + let mut polys = Vec::with_capacity(secret.len()); + + 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, 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) -> Result { - 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>) -> Result, &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> = 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 922a9eb..10be8b7 100644 --- a/src/math.rs +++ b/src/math.rs @@ -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, p: u128) -> u128 { - (points - .iter() - .enumerate() - .map(|(j, (xj, yj))| { - 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>) -> Vec { + let n_chunks = shares.values().take(1).collect::>>()[0].len(); + + (0..n_chunks) + .map(|s| { + shares .iter() - .enumerate() - .filter(|(m, _)| *m != j) - .map(|(_, (xm, _))| div_diff_mod(xm, xj, p)) - .product::() - * 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::() + * y_i[s] + }) + .sum::() + .0 }) - .sum::() - % p) - .to_u128() - .unwrap() + .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) { - let mut coeffs = Vec::with_capacity(k); - let between = Uniform::new(1, p); +pub fn random_polynomial(s: GF256, k: u8) -> Vec { + 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, p: u128) -> impl Iterator { - (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>) -> impl Iterator)> { + (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]); } } diff --git a/src/mersenne.rs b/src/mersenne.rs deleted file mode 100644 index 233db9a..0000000 --- a/src/mersenne.rs +++ /dev/null @@ -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];