blob: c1c22cd0abc10fd7d9f9a892a4b103611c27c863 [file] [log] [blame]
// Copyright (c) 2016 rust-threshold-secret-sharing developers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.
//! Packed (or ramp) variant of Shamir secret sharing,
//! allowing efficient sharing of several secrets together.
use numtheory::{mod_pow, fft2_inverse, fft3};
use rand;
use std::vec::Vec;
/// Parameters for the packed variant of Shamir secret sharing,
/// specifying number of secrets shared together, total number of shares, and privacy threshold.
///
/// This scheme generalises
/// [Shamir's scheme](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing)
/// by simultaneously sharing several secrets, at the expense of leaving a gap
/// between the privacy threshold and the reconstruction limit.
///
/// The Fast Fourier Transform is used for efficiency reasons,
/// allowing most operations run to quasilinear time `O(n.log(n))` in `share_count`.
/// An implication of this is that secrets and shares are positioned on positive powers of
/// respectively an `n`-th and `m`-th principal root of unity,
/// where `n` is a power of 2 and `m` a power of 3.
///
/// As a result there exist several constraints between the various parameters:
///
/// * `prime` must be a prime large enough to hold the secrets we plan to share
/// * `share_count` must be at least `secret_count + threshold` (the reconstruction limit)
/// * `secret_count + threshold + 1` must be a power of 2
/// * `share_count + 1` must be a power of 3
/// * `omega_secrets` must be a `(secret_count + threshold + 1)`-th root of unity
/// * `omega_shares` must be a `(share_count + 1)`-th root of unity
///
/// An optional `paramgen` feature provides methods for finding suitable parameters satisfying
/// these somewhat complex requirements, in addition to several fixed parameter choices.
#[derive(Debug,Copy,Clone,PartialEq)]
pub struct PackedSecretSharing {
// abstract properties
/// Maximum number of shares that can be known without exposing the secrets
/// (privacy threshold).
pub threshold: usize,
/// Number of shares to split the secrets into.
pub share_count: usize,
/// Number of secrets to share together.
pub secret_count: usize,
// implementation configuration
/// Prime defining the Zp field in which computation is taking place.
pub prime: i64,
/// `m`-th principal root of unity in Zp, where `m = secret_count + threshold + 1`
/// must be a power of 2.
pub omega_secrets: i64,
/// `n`-th principal root of unity in Zp, where `n = share_count + 1` must be a power of 3.
pub omega_shares: i64,
}
/// Example of tiny PSS settings, for sharing 3 secrets into 8 shares, with
/// a privacy threshold of 4.
pub static PSS_4_8_3: PackedSecretSharing = PackedSecretSharing {
threshold: 4,
share_count: 8,
secret_count: 3,
prime: 433,
omega_secrets: 354,
omega_shares: 150,
};
/// Example of small PSS settings, for sharing 3 secrets into 26 shares, with
/// a privacy threshold of 4.
pub static PSS_4_26_3: PackedSecretSharing = PackedSecretSharing {
threshold: 4,
share_count: 26,
secret_count: 3,
prime: 433,
omega_secrets: 354,
omega_shares: 17,
};
/// Example of PSS settings, for sharing 100 secrets into 728 shares, with
/// a privacy threshold of 155.
pub static PSS_155_728_100: PackedSecretSharing = PackedSecretSharing {
threshold: 155,
share_count: 728,
secret_count: 100,
prime: 746497,
omega_secrets: 95660,
omega_shares: 610121,
};
/// Example of PSS settings, for sharing 100 secrets into 19682 shares, with
/// a privacy threshold of 155.
pub static PSS_155_19682_100: PackedSecretSharing = PackedSecretSharing {
threshold: 155,
share_count: 19682,
secret_count: 100,
prime: 5038849,
omega_secrets: 4318906,
omega_shares: 1814687,
};
impl PackedSecretSharing {
/// Minimum number of shares required to reconstruct secrets.
///
/// For this scheme this is always `secret_count + threshold`
pub fn reconstruct_limit(&self) -> usize {
self.threshold + self.secret_count
}
/// Generate `share_count` shares for the `secrets` vector.
///
/// The length of `secrets` must be `secret_count`.
/// It is safe to pad with anything, including zeros.
pub fn share(&self, secrets: &[i64]) -> Vec<i64> {
assert_eq!(secrets.len(), self.secret_count);
// sample polynomial
let mut poly = self.sample_polynomial(secrets);
assert_eq!(poly.len(), self.reconstruct_limit() + 1);
// .. and extend it
poly.extend(vec![0; self.share_count - self.reconstruct_limit()]);
assert_eq!(poly.len(), self.share_count + 1);
// evaluate polynomial to generate shares
let mut shares = self.evaluate_polynomial(poly);
// .. but remove first element since it should not be used as a share (it's always zero)
assert_eq!(shares[0], 0);
shares.remove(0);
// return
assert_eq!(shares.len(), self.share_count);
shares
}
fn sample_polynomial(&self, secrets: &[i64]) -> Vec<i64> {
assert_eq!(secrets.len(), self.secret_count);
// sample randomness using secure randomness
use rand::distributions::Sample;
let mut range = rand::distributions::range::Range::new(0, self.prime - 1);
let mut rng = rand::SgxRng::new().unwrap();
let randomness: Vec<i64> =
(0..self.threshold).map(|_| range.sample(&mut rng) as i64).collect();
// recover polynomial
let coefficients = self.recover_polynomial(secrets, randomness);
assert_eq!(coefficients.len(), self.reconstruct_limit() + 1);
coefficients
}
fn recover_polynomial(&self, secrets: &[i64], randomness: Vec<i64>) -> Vec<i64> {
// fix the value corresponding to point 1 (zero)
let mut values: Vec<i64> = vec![0];
// let the subsequent values correspond to the secrets
values.extend(secrets);
// fill in with random values
values.extend(randomness);
// run backward FFT to recover polynomial in coefficient representation
assert_eq!(values.len(), self.reconstruct_limit() + 1);
let coefficients = fft2_inverse(&values, self.omega_secrets, self.prime);
coefficients
}
fn evaluate_polynomial(&self, coefficients: Vec<i64>) -> Vec<i64> {
assert_eq!(coefficients.len(), self.share_count + 1);
let points = fft3(&coefficients, self.omega_shares, self.prime);
points
}
/// Reconstruct the secrets from a large enough subset of the shares.
///
/// `indices` are the ranks of the known shares as output by the `share` method,
/// while `values` are the actual values of these shares.
/// Both must have the same number of elements, and at least `reconstruct_limit`.
///
/// The resulting vector is of length `secret_count`.
pub fn reconstruct(&self, indices: &[usize], shares: &[i64]) -> Vec<i64> {
assert!(shares.len() == indices.len());
assert!(shares.len() >= self.reconstruct_limit());
let mut points: Vec<i64> =
indices.iter()
.map(|&x| mod_pow(self.omega_shares, x as u32 + 1, self.prime))
.collect();
let mut values = shares.to_vec();
// insert missing value for point 1 (zero)
points.insert(0, 1);
values.insert(0, 0);
// interpolate using Newton's method
use numtheory::{newton_interpolation_general, newton_evaluate};
// TODO optimise by using Newton-equally-space variant
let poly = newton_interpolation_general(&points, &values, self.prime);
// evaluate at omega_secrets points to recover secrets
// TODO optimise to avoid re-computation of power
let secrets = (1..self.reconstruct_limit())
.map(|e| mod_pow(self.omega_secrets, e as u32, self.prime))
.map(|point| newton_evaluate(&poly, point, self.prime))
.take(self.secret_count)
.collect();
secrets
}
}
#[cfg(test)]
mod tests {
use super::*;
use numtheory::*;
#[test]
fn test_recover_polynomial() {
let ref pss = PSS_4_8_3;
let secrets = vec![1, 2, 3];
let randomness = vec![8, 8, 8, 8]; // use fixed randomness
let poly = pss.recover_polynomial(&secrets, randomness);
assert_eq!(
positivise(&poly, pss.prime),
positivise(&[113, -382, -172, 267, -325, 432, 388, -321], pss.prime)
);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_evaluate_polynomial() {
let ref pss = PSS_4_26_3;
let poly = vec![113, 51, 261, 267, 108, 432, 388, 112, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0];
let points = &pss.evaluate_polynomial(poly);
assert_eq!(
positivise(points, pss.prime),
vec![ 0, 77, 230, 91, 286, 179, 337, 83, 212,
88, 406, 58, 425, 345, 350, 336, 430, 404,
51, 60, 305, 395, 84, 156, 160, 112, 422]
);
}
#[test]
#[cfg_attr(rustfmt, rustfmt_skip)]
fn test_share() {
let ref pss = PSS_4_26_3;
// do sharing
let secrets = vec![5, 6, 7];
let mut shares = pss.share(&secrets);
// manually recover secrets
use numtheory::{fft3_inverse, mod_evaluate_polynomial};
shares.insert(0, 0);
let poly = fft3_inverse(&shares, PSS_4_26_3.omega_shares, PSS_4_26_3.prime);
let recovered_secrets: Vec<i64> = (1..secrets.len() + 1)
.map(|i| {
mod_evaluate_polynomial(&poly,
mod_pow(PSS_4_26_3.omega_secrets,
i as u32,
PSS_4_26_3.prime),
PSS_4_26_3.prime)
})
.collect();
use numtheory::positivise;
assert_eq!(positivise(&recovered_secrets, pss.prime), secrets);
}
#[test]
fn test_large_share() {
let ref pss = PSS_155_19682_100;
let secrets = vec![5 ; pss.secret_count];
let shares = pss.share(&secrets);
assert_eq!(shares.len(), pss.share_count);
}
#[test]
fn test_share_reconstruct() {
let ref pss = PSS_4_26_3;
let secrets = vec![5, 6, 7];
let shares = pss.share(&secrets);
use numtheory::positivise;
// reconstruction must work for all shares
let indices: Vec<usize> = (0..shares.len()).collect();
let recovered_secrets = pss.reconstruct(&indices, &shares);
assert_eq!(positivise(&recovered_secrets, pss.prime), secrets);
// .. and for only sufficient shares
let indices: Vec<usize> = (0..pss.reconstruct_limit()).collect();
let recovered_secrets = pss.reconstruct(&indices, &shares[0..pss.reconstruct_limit()]);
print!("lenght is {:?}", indices.len());
assert_eq!(positivise(&recovered_secrets, pss.prime), secrets);
}
#[test]
fn test_share_additive_homomorphism() {
let ref pss = PSS_4_26_3;
let secrets_1 = vec![1, 2, 3];
let secrets_2 = vec![4, 5, 6];
let shares_1 = pss.share(&secrets_1);
let shares_2 = pss.share(&secrets_2);
// add shares pointwise
let shares_sum: Vec<i64> =
shares_1.iter().zip(shares_2).map(|(a, b)| (a + b) % pss.prime).collect();
// reconstruct sum, using same reconstruction limit
let reconstruct_limit = pss.reconstruct_limit();
let indices: Vec<usize> = (0..reconstruct_limit).collect();
let shares = &shares_sum[0..reconstruct_limit];
let recovered_secrets = pss.reconstruct(&indices, shares);
use numtheory::positivise;
assert_eq!(positivise(&recovered_secrets, pss.prime), vec![5, 7, 9]);
}
#[test]
fn test_share_multiplicative_homomorphism() {
let ref pss = PSS_4_26_3;
let secrets_1 = vec![1, 2, 3];
let secrets_2 = vec![4, 5, 6];
let shares_1 = pss.share(&secrets_1);
let shares_2 = pss.share(&secrets_2);
// multiply shares pointwise
let shares_product: Vec<i64> =
shares_1.iter().zip(shares_2).map(|(a, b)| (a * b) % pss.prime).collect();
// reconstruct product, using double reconstruction limit
let reconstruct_limit = pss.reconstruct_limit() * 2;
let indices: Vec<usize> = (0..reconstruct_limit).collect();
let shares = &shares_product[0..reconstruct_limit];
let recovered_secrets = pss.reconstruct(&indices, shares);
use numtheory::positivise;
assert_eq!(positivise(&recovered_secrets, pss.prime), vec![4, 10, 18]);
}
}
#[doc(hidden)]
#[cfg(feature = "paramgen")]
pub mod paramgen {
//! Optional helper methods for parameter generation
extern crate primal;
#[cfg_attr(rustfmt, rustfmt_skip)]
fn check_prime_form(min_p: usize, n: usize, m: usize, p: usize) -> bool {
if p < min_p { return false; }
let q = p - 1;
if q % n != 0 { return false; }
if q % m != 0 { return false; }
let q = q / (n * m);
if q % n == 0 { return false; }
if q % m == 0 { return false; }
return true;
}
#[test]
fn test_check_prime_form() {
assert_eq!(primal::Primes::all().find(|p| check_prime_form(198, 8, 9, *p)).unwrap(), 433);
}
fn factor(p: usize) -> Vec<usize> {
let mut factors = vec![];
let bound = (p as f64).sqrt().ceil() as usize;
for f in 2..bound + 1 {
if p % f == 0 {
factors.push(f);
factors.push(p / f);
}
}
factors
}
#[test]
fn test_factor() {
assert_eq!(factor(40), [2, 20, 4, 10, 5, 8]);
assert_eq!(factor(41), []);
}
fn find_field(min_p: usize, n: usize, m: usize) -> Option<(i64, i64)> {
// find prime of right form
let p = primal::Primes::all().find(|p| check_prime_form(min_p, n, m, *p)).unwrap();
// find (any) generator
let factors = factor(p - 1);
for g in 2..p {
// test generator against all factors of p-1
let is_generator = factors.iter().all(|f| {
use numtheory::mod_pow;
let e = (p - 1) / f;
mod_pow(g as i64, e as u32, p as i64) != 1 // TODO check for negative value
});
// return
if is_generator {
return Some((p as i64, g as i64));
}
}
// didn't find any
None
}
#[test]
fn test_find_field() {
assert_eq!(find_field(198, 2usize.pow(3), 3usize.pow(2)).unwrap(),
(433, 5));
assert_eq!(find_field(198, 2usize.pow(3), 3usize.pow(3)).unwrap(),
(433, 5));
assert_eq!(find_field(198, 2usize.pow(8), 3usize.pow(6)).unwrap(),
(746497, 5));
assert_eq!(find_field(198, 2usize.pow(8), 3usize.pow(9)).unwrap(),
(5038849, 29));
// assert_eq!(find_field(198, 2usize.pow(11), 3usize.pow(8)).unwrap(), (120932353, 5));
// assert_eq!(find_field(198, 2usize.pow(13), 3usize.pow(9)).unwrap(), (483729409, 23));
}
fn find_roots(n: usize, m: usize, p: i64, g: i64) -> (i64, i64) {
use numtheory::mod_pow;
let omega_secrets = mod_pow(g, ((p - 1) / n as i64) as u32, p);
let omega_shares = mod_pow(g, ((p - 1) / m as i64) as u32, p);
(omega_secrets, omega_shares)
}
#[test]
fn test_find_roots() {
assert_eq!(find_roots(2usize.pow(3), 3usize.pow(2), 433, 5), (354, 150));
assert_eq!(find_roots(2usize.pow(3), 3usize.pow(3), 433, 5), (354, 17));
}
#[doc(hidden)]
pub fn generate_parameters(min_size: usize, n: usize, m: usize) -> (i64, i64, i64) {
// TODO settle option business once and for all (don't remember it as needed)
let (prime, g) = find_field(min_size, n, m).unwrap();
let (omega_secrets, omega_shares) = find_roots(n, m, prime, g);
(prime, omega_secrets, omega_shares)
}
#[test]
fn test_generate_parameters() {
assert_eq!(generate_parameters(200, 2usize.pow(3), 3usize.pow(2)),
(433, 354, 150));
assert_eq!(generate_parameters(200, 2usize.pow(3), 3usize.pow(3)),
(433, 354, 17));
}
fn is_power_of(x: usize, e: usize) -> bool {
let power = (x as f64).log(e as f64).floor() as u32;
e.pow(power) == x
}
#[test]
fn test_is_power_of() {
assert_eq!(is_power_of(4, 2), true);
assert_eq!(is_power_of(5, 2), false);
assert_eq!(is_power_of(6, 2), false);
assert_eq!(is_power_of(7, 2), false);
assert_eq!(is_power_of(8, 2), true);
assert_eq!(is_power_of(4, 3), false);
assert_eq!(is_power_of(5, 3), false);
assert_eq!(is_power_of(6, 3), false);
assert_eq!(is_power_of(7, 3), false);
assert_eq!(is_power_of(8, 3), false);
assert_eq!(is_power_of(9, 3), true);
}
use super::PackedSecretSharing;
impl PackedSecretSharing {
/// Find suitable parameters with as small a prime field as possible.
pub fn new(threshold: usize,
secret_count: usize,
share_count: usize)
-> PackedSecretSharing {
let min_size = share_count + secret_count + threshold + 1;
Self::new_with_min_size(threshold, secret_count, share_count, min_size)
}
/// Find suitable parameters with a prime field of at least the specified size.
pub fn new_with_min_size(threshold: usize,
secret_count: usize,
share_count: usize,
min_size: usize)
-> PackedSecretSharing {
let m = threshold + secret_count + 1;
let n = share_count + 1;
assert!(is_power_of(m, 2));
assert!(is_power_of(n, 3));
assert!(min_size >= share_count + secret_count + threshold + 1);
let (prime, omega_secrets, omega_shares) = generate_parameters(min_size, m, n);
PackedSecretSharing {
threshold: threshold,
share_count: share_count,
secret_count: secret_count,
prime: prime,
omega_secrets: omega_secrets,
omega_shares: omega_shares,
}
}
}
#[test]
fn test_new() {
assert_eq!(PackedSecretSharing::new(155, 100, 728),
super::PSS_155_728_100);
assert_eq!(PackedSecretSharing::new_with_min_size(4, 3, 8, 200),
super::PSS_4_8_3);
assert_eq!(PackedSecretSharing::new_with_min_size(4, 3, 26, 200),
super::PSS_4_26_3);
}
}