changed arithmetic to GF256, unlimited secret length, updated docs (#1)
This commit is contained in:
parent
a3e43f373a
commit
5344dda94b
12
CHANGELOG.md
12
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
|
||||
|
|
|
@ -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"
|
||||
|
|
15
README.md
15
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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
182
src/lib.rs
182
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<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]);
|
||||
}
|
||||
}
|
||||
|
|
147
src/math.rs
147
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<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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
Loading…
Reference in New Issue