diff --git a/.travis.yml b/.travis.yml index 13be6b3c..cd35f73d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,38 +1,50 @@ - -sudo: required - language: rust +cache: cargo # https://docs.travis-ci.com/user/caching/#Rust-Cargo-cache rust: - stable - beta - - nightly matrix: + # Since this item is allowed to fail, don't wait for it's result to mark the + # build complete. + fast_finish: true allow_failures: - - rust: nightly + - env: NAME='nightly' + - env: NAME='kcov' + include: + - env: NAME='nightly' + rust: nightly + - env: NAME='rustfmt' + rust: nightly + before_script: + - rustup component add rustfmt-preview + script: + - cargo fmt --all -- --write-mode=diff + - env: NAME='kcov' + sudo: required # travis-ci/travis-ci#9061 + before_script: + - cargo install cargo-update || echo "cargo-update already installed" + - cargo install cargo-kcov || echo "cargo-kcov already installed" + - cargo install-update -a + script: + - cargo kcov --print-install-kcov-sh | sh + - cargo update # Creates `Cargo.lock` needed by next command + - cargo kcov --verbose --features dss --coveralls -- --verify --exclude-pattern=/.cargo,/usr/lib,src/proto + addons: + apt: + packages: + - libcurl4-openssl-dev + - libdw-dev + - binutils-dev + - libiberty-dev + - zlib1g-dev env: global: - RUSTFLAGS="-C link-dead-code" -addons: - apt: - packages: - - libcurl4-openssl-dev - - libdw-dev - - cmake - - g++ - - pkg-config - - binutils-dev - - libiberty-dev - script: - cargo build --verbose --all-features - cargo test --verbose --all-features - -after_success: - - cargo install cargo-kcov - - cargo kcov --print-install-kcov-sh | sh - - cargo kcov --verbose --features dss --coveralls -- --verify --exclude-pattern=/.cargo,/usr/lib,src/proto - + - cargo doc --verbose --all-features diff --git a/Cargo.toml b/Cargo.toml index 9baeef13..4da5b1af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ dss = [] [dependencies] base64 = "0.9.0" -rand = "^0.3" +rand = "^0.4.2" ring = "^0.12" merkle_sigs = "^1.4" protobuf = "^1.4" @@ -63,4 +63,3 @@ tag-prefix = "v" tag-message = "Release version {{version}}." doc-commit-message = "Update documentation." dev-version-ext = "pre" - diff --git a/benches/ss1.rs b/benches/ss1.rs index 601e92f6..f6e993a0 100644 --- a/benches/ss1.rs +++ b/benches/ss1.rs @@ -1,5 +1,6 @@ #![cfg(test)] #![feature(test)] +#![cfg(feature = "dss")] extern crate rusty_secrets; extern crate test; @@ -9,29 +10,37 @@ mod shared; mod ss1 { use rusty_secrets::dss::ss1; - use test::{black_box, Bencher}; use shared; + use test::{black_box, Bencher}; macro_rules! bench_generate { - ($name:ident, $k:expr, $n:expr, $secret:ident) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); b.iter(move || { - let shares = ss1::split_secret($k, $n, &secret, ss1::Reproducibility::reproducible(), &None).unwrap(); + let shares = ss1::split_secret( + $k, + $n, + &secret, + ss1::Reproducibility::reproducible(), + &None, + ).unwrap(); black_box(shares); }); } - ) + }; } macro_rules! bench_recover { - ($name:ident, $k:expr, $n:expr, $secret:ident) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); - let all_shares = ss1::split_secret($k, $n, &secret, ss1::Reproducibility::reproducible(), &None).unwrap(); + let all_shares = + ss1::split_secret($k, $n, &secret, ss1::Reproducibility::reproducible(), &None) + .unwrap(); let shares = &all_shares.into_iter().take($k).collect::>().clone(); b.iter(|| { @@ -39,7 +48,7 @@ mod ss1 { black_box(result); }); } - ) + }; } bench_generate!(generate_1kb_3_5, 3, 5, secret_1kb); diff --git a/benches/sss.rs b/benches/sss.rs index b48b6aac..65ead565 100644 --- a/benches/sss.rs +++ b/benches/sss.rs @@ -8,12 +8,12 @@ mod shared; mod sss { - use test::{black_box, Bencher}; use rusty_secrets::sss; use shared; + use test::{black_box, Bencher}; macro_rules! bench_generate { - ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); @@ -23,11 +23,11 @@ mod sss { black_box(shares); }); } - ) + }; } macro_rules! bench_recover { - ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); @@ -39,7 +39,7 @@ mod sss { black_box(result); }); } - ) + }; } bench_generate!(generate_1kb_3_5, 3, 5, secret_1kb, false); diff --git a/benches/thss.rs b/benches/thss.rs index 95dc79c2..c4dd97d5 100644 --- a/benches/thss.rs +++ b/benches/thss.rs @@ -1,5 +1,6 @@ #![cfg(test)] #![feature(test)] +#![cfg(feature = "dss")] extern crate rusty_secrets; extern crate test; @@ -9,11 +10,11 @@ mod shared; mod thss { use rusty_secrets::dss::thss; - use test::{black_box, Bencher}; use shared; + use test::{black_box, Bencher}; macro_rules! bench_generate { - ($name:ident, $k:expr, $n:expr, $secret:ident) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); @@ -23,11 +24,11 @@ mod thss { black_box(shares); }); } - ) + }; } macro_rules! bench_recover { - ($name:ident, $k:expr, $n:expr, $secret:ident) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); @@ -39,7 +40,7 @@ mod thss { black_box(result); }); } - ) + }; } bench_generate!(generate_1kb_3_5, 3, 5, secret_1kb); diff --git a/benches/wrapped_secrets.rs b/benches/wrapped_secrets.rs index 627f3766..af571419 100644 --- a/benches/wrapped_secrets.rs +++ b/benches/wrapped_secrets.rs @@ -8,30 +8,32 @@ mod shared; mod wrapped_secrets { - use test::{black_box, Bencher}; use rusty_secrets::wrapped_secrets; use shared; + use test::{black_box, Bencher}; macro_rules! bench_generate { - ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); b.iter(move || { - let shares = wrapped_secrets::split_secret($k, $n, secret, None, $signed).unwrap(); + let shares = + wrapped_secrets::split_secret($k, $n, secret, None, $signed).unwrap(); black_box(shares); }); } - ) + }; } macro_rules! bench_recover { - ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => ( + ($name:ident, $k:expr, $n:expr, $secret:ident, $signed:expr) => { #[bench] fn $name(b: &mut Bencher) { let secret = shared::$secret(); - let all_shares = wrapped_secrets::split_secret($k, $n, &secret, None, $signed).unwrap(); + let all_shares = + wrapped_secrets::split_secret($k, $n, &secret, None, $signed).unwrap(); let shares = all_shares.into_iter().take($k).collect::>(); b.iter(|| { @@ -39,7 +41,7 @@ mod wrapped_secrets { black_box(result); }); } - ) + }; } bench_generate!(generate_1kb_3_5, 3, 5, secret_1kb, false); diff --git a/build.rs b/build.rs index f1488f4e..a4d50197 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,9 @@ use std::env; +use std::fmt; use std::fs::File; use std::io::Write; -use std::path::Path; -use std::fmt; use std::num::Wrapping; +use std::path::Path; const POLY: u8 = 0x1D; @@ -78,8 +78,8 @@ fn main() { write!( f, "pub struct Tables {{ \ - pub exp: [u8; 256], \ - pub log: [u8; 256] \ + pub exp: [u8; 256], \ + pub log: [u8; 256] \ }} \ \ pub static TABLES: Tables = " diff --git a/src/dss/format.rs b/src/dss/format.rs index 70669b5b..c4d0386c 100644 --- a/src/dss/format.rs +++ b/src/dss/format.rs @@ -1,7 +1,7 @@ use std::error::Error; -use protobuf::{self, Message}; use base64; +use protobuf::{self, Message}; use errors::*; use proto::dss::ShareProto; diff --git a/src/dss/metadata.rs b/src/dss/metadata.rs index 86c3f924..fa11f24e 100644 --- a/src/dss/metadata.rs +++ b/src/dss/metadata.rs @@ -1,5 +1,5 @@ -use std::collections::BTreeMap; use ring::digest; +use std::collections::BTreeMap; /// A share's public metadata. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] diff --git a/src/dss/mod.rs b/src/dss/mod.rs index ab45caa7..f7739d28 100644 --- a/src/dss/mod.rs +++ b/src/dss/mod.rs @@ -27,8 +27,8 @@ //! **ErrDet** | An inauthentic set of shares produced by an adversary will be flagged as such when fed to the recovery algorithm. //! **Repro** | Share reproducible: The scheme can produce shares in a deterministic way. -pub mod thss; pub mod ss1; +pub mod thss; mod metadata; diff --git a/src/dss/ss1/mod.rs b/src/dss/ss1/mod.rs index 78335d2e..18a4b066 100644 --- a/src/dss/ss1/mod.rs +++ b/src/dss/ss1/mod.rs @@ -29,8 +29,8 @@ mod share; pub use self::share::*; mod scheme; -use self::scheme::SS1; pub use self::scheme::Reproducibility; +use self::scheme::SS1; use dss::AccessStructure; diff --git a/src/dss/ss1/scheme.rs b/src/dss/ss1/scheme.rs index 0801a286..01ad67d1 100644 --- a/src/dss/ss1/scheme.rs +++ b/src/dss/ss1/scheme.rs @@ -1,17 +1,17 @@ use std::collections::HashSet; -use ring::{hkdf, hmac}; -use ring::rand::{SecureRandom, SystemRandom}; -use ring::digest::{Context, SHA256}; use rand::{ChaChaRng, Rng, SeedableRng}; +use ring::digest::{Context, SHA256}; +use ring::rand::{SecureRandom, SystemRandom}; +use ring::{hkdf, hmac}; -use errors::*; -use dss::{thss, AccessStructure}; -use dss::thss::{MetaData, ThSS}; -use dss::random::{random_bytes_count, FixedRandom, MAX_MESSAGE_SIZE}; -use share::validation::{validate_share_count, validate_shares}; use super::share::*; +use dss::random::{random_bytes_count, FixedRandom, MAX_MESSAGE_SIZE}; +use dss::thss::{MetaData, ThSS}; use dss::utils; +use dss::{thss, AccessStructure}; +use errors::*; +use share::validation::{validate_all_shares, validate_share_count}; use vol_hash::VOLHash; /// We bound the message size at about 16MB to avoid overflow in `random_bytes_count`. @@ -247,13 +247,14 @@ impl SS1 { &self, shares: &[Share], ) -> Result<(Vec, AccessStructure, Option)> { - let (_, shares) = validate_shares(shares.to_vec())?; + let shares = shares.to_vec(); + let (threshold, _) = validate_all_shares(&shares)?; let underlying_shares = shares .iter() .map(|share| thss::Share { id: share.id, - threshold: share.threshold, + threshold, shares_count: share.shares_count, data: share.data.clone(), metadata: share.metadata.clone(), diff --git a/src/dss/ss1/serialize.rs b/src/dss/ss1/serialize.rs index ca040df2..15b48d91 100644 --- a/src/dss/ss1/serialize.rs +++ b/src/dss/ss1/serialize.rs @@ -1,8 +1,8 @@ -use errors::*; use super::{MetaData, Share}; use dss::format::{format_share_protobuf, parse_share_protobuf}; -use proto::dss::{MetaDataProto, ShareProto}; use dss::utils::{btreemap_to_hashmap, hashmap_to_btreemap}; +use errors::*; +use proto::dss::{MetaDataProto, ShareProto}; pub(crate) fn share_to_string(share: Share) -> String { let proto = share_to_protobuf(share); diff --git a/src/dss/ss1/share.rs b/src/dss/ss1/share.rs index 4ca41788..d1f6fccb 100644 --- a/src/dss/ss1/share.rs +++ b/src/dss/ss1/share.rs @@ -1,6 +1,6 @@ +use super::serialize::{share_from_string, share_to_string}; use errors::*; use share::IsShare; -use super::serialize::{share_from_string, share_to_string}; pub use dss::metadata::MetaData; diff --git a/src/dss/thss/scheme.rs b/src/dss/thss/scheme.rs index c11cfd70..85dcb2b5 100644 --- a/src/dss/thss/scheme.rs +++ b/src/dss/thss/scheme.rs @@ -4,15 +4,15 @@ use std::fmt; use ring::rand::{SecureRandom, SystemRandom}; +use dss::random::{random_bytes, random_bytes_count, MAX_MESSAGE_SIZE}; use errors::*; use gf256::Gf256; -use dss::random::{random_bytes, random_bytes_count, MAX_MESSAGE_SIZE}; -use share::validation::{validate_share_count, validate_shares}; use lagrange; +use share::validation::{validate_all_shares, validate_share_count}; use super::AccessStructure; -use super::share::*; use super::encode::encode_secret; +use super::share::*; /// We bound the message size at about 16MB to avoid overflow in `random_bytes_count`. /// Moreover, given the current performances, it is almost unpractical to run @@ -89,9 +89,8 @@ impl ThSS { &self, shares: &[Share], ) -> Result<(Vec, AccessStructure, Option)> { - let (threshold, shares) = validate_shares(shares.to_vec())?; - - let cypher_len = shares[0].data.len(); + let shares = shares.to_vec(); + let (threshold, cypher_len) = validate_all_shares(&shares)?; let polys = (0..cypher_len) .map(|i| { diff --git a/src/dss/thss/serialize.rs b/src/dss/thss/serialize.rs index 71909348..1111f55e 100644 --- a/src/dss/thss/serialize.rs +++ b/src/dss/thss/serialize.rs @@ -1,8 +1,8 @@ -use errors::*; use super::{MetaData, Share}; use dss::format::{format_share_protobuf, parse_share_protobuf}; -use proto::dss::{MetaDataProto, ShareProto}; use dss::utils::{btreemap_to_hashmap, hashmap_to_btreemap}; +use errors::*; +use proto::dss::{MetaDataProto, ShareProto}; pub(crate) fn share_to_string(share: Share) -> String { let proto = share_to_protobuf(share); diff --git a/src/dss/thss/share.rs b/src/dss/thss/share.rs index 15a942bd..f68bf15e 100644 --- a/src/dss/thss/share.rs +++ b/src/dss/thss/share.rs @@ -1,6 +1,6 @@ +use super::serialize::{share_from_string, share_to_string}; use errors::*; use share::IsShare; -use super::serialize::{share_from_string, share_to_string}; pub use dss::metadata::MetaData; diff --git a/src/dss/utils.rs b/src/dss/utils.rs index 59a23622..20a0fedf 100644 --- a/src/dss/utils.rs +++ b/src/dss/utils.rs @@ -1,7 +1,7 @@ use std; -use std::hash::Hash; use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; /// Transmutes a `&[u8]` into a `&[u32]`. /// Despite `std::mem::transmute` being very unsafe in diff --git a/src/errors.rs b/src/errors.rs index c2b74961..81fcf81f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,6 +3,7 @@ #![allow(unknown_lints, missing_docs)] use std::collections::HashSet; +use std::fmt; #[cfg(feature = "dss")] use dss::ss1; @@ -59,16 +60,17 @@ error_chain! { display("The shares are incompatible with each other.") } - ShareIdentifierTooBig(id: u8, n: u8) { - description("Share identifier too big") - display("Found share identifier ({}) bigger than the maximum number of shares ({}).", id, n) - } - - MissingShares(provided: usize, required: usize) { + MissingShares(provided: u8, required: u8) { description("The number of shares provided is insufficient to recover the secret.") display("{} shares are required to recover the secret, found only {}.", required, provided) } + NoMoreSharesNeeded(required: u8) { + description("The number of shares evaluated has already met the threshold and the + secret is available.") + display("Only {} shares are required to recover the secret. The secret should already be available.", required) + } + InvalidSignature(share_id: u8, signature: String) { description("The signature of this share is not valid.") } @@ -97,6 +99,11 @@ error_chain! { display("Found invalid share identifier ({})", share_id) } + ShareParsingInvalidShareThreshold(k: u8, id: u8) { + description("Threshold k must be bigger than or equal to 2") + display("Threshold k must be bigger than or equal to 2. Got k = {} for share identifier {}.", k, id) + } + InvalidSS1Parameters(r: usize, s: usize) { description("Invalid parameters for the SS1 sharing scheme") display("Invalid parameters for the SS1 sharing scheme: r = {}, s = {}.", r, s) @@ -123,15 +130,25 @@ error_chain! { display("This share number ({}) has already been used by a previous share.", share_id) } - DuplicateShareData(share_id: u8) { - description("The data encoded in this share is the same as the one found in a previous share") - display("The data encoded in share #{} is the same as the one found in a previous share.", share_id) + InconsistentSecretLengths(id: u8, slen_: usize, ids: Vec, slen: usize) { + description("The shares are incompatible with each other because they do not all have the same secret length.") + display("The share identifier {} had secret length {}, while the secret length {} was found for share identifier(s): {}.", id, slen_, slen, no_more_than_five(ids)) + } + + InconsistentSignatures(id: u8, ids: Vec) { + description("The shares are incompatible with each other because they have valid signatures from different keys.") + display("The share identifier {} was signed by a different key than share identifier(s): {}.", id, no_more_than_five(ids)) } InconsistentShares { description("The shares are inconsistent") display("The shares are inconsistent") } + + InconsistentThresholds(id: u8, k_: u8, ids: Vec, k: u8) { + description("The shares are incompatible with each other because they do not all have the same threshold.") + display("The share identifier {} had k = {}, while k = {} was found for share identifier(s): {}.", id, k_, k, no_more_than_five(ids)) + } } foreign_links { @@ -139,3 +156,19 @@ error_chain! { IntegerParsingError(::std::num::ParseIntError); } } + +/// Takes a `Vec` and formats it like the normal `fmt::Debug` implementation, unless it has more +//than five elements, in which case the rest are replaced by ellipsis. +fn no_more_than_five(vec: &[T]) -> String { + let len = vec.len(); + if len > 5 { + let mut string = String::from("["); + for item in vec.iter().take(5) { + string += &format!("{}, ", item); + } + string.push_str("...]"); + string + } else { + format!("{:?}", vec) + } +} diff --git a/src/gf256.rs b/src/gf256.rs index 23546d91..10c7d1a4 100644 --- a/src/gf256.rs +++ b/src/gf256.rs @@ -66,6 +66,7 @@ impl Gf256 { } } +#[allow(suspicious_arithmetic_impl)] impl Add for Gf256 { type Output = Gf256; #[inline] @@ -81,6 +82,7 @@ impl AddAssign for Gf256 { } } +#[allow(suspicious_arithmetic_impl)] impl Sub for Gf256 { type Output = Gf256; #[inline] @@ -143,7 +145,9 @@ impl Neg for Gf256 { #[macro_export] #[doc(hidden)] macro_rules! gf256 { - ($e:expr) => (Gf256::from_byte($e)) + ($e:expr) => { + Gf256::from_byte($e) + }; } #[macro_export] @@ -178,10 +182,10 @@ mod tests { mod vectors { use super::*; + use flate2::read::GzDecoder; + use itertools::Itertools; use std::fs::File; use std::io::{BufRead, BufReader}; - use itertools::Itertools; - use flate2::read::GzDecoder; macro_rules! mk_test { ($id:ident, $op:expr, $val:expr) => { @@ -196,7 +200,8 @@ mod tests { }); let ref_path = format!("tests/fixtures/gf256/gf256_{}.txt.gz", stringify!($id)); - let reference = BufReader::new(GzDecoder::new(File::open(ref_path).unwrap()).unwrap()); + let reference = + BufReader::new(GzDecoder::new(File::open(ref_path).unwrap()).unwrap()); for ((i, j, k), line) in results.zip(reference.lines()) { let left = format!("{} {} {} = {}", i, $op, j, k); @@ -204,7 +209,7 @@ mod tests { assert_eq!(left, right); } } - } + }; } mk_test!(add, "+", |i: Gf256, j: Gf256| i + j); diff --git a/src/lagrange.rs b/src/lagrange.rs index 50baf8b5..368a99ce 100644 --- a/src/lagrange.rs +++ b/src/lagrange.rs @@ -1,36 +1,177 @@ +/// Implements barycentric Lagrange interpolation. + +use errors::*; use gf256::Gf256; use poly::Poly; -/// Evaluates an interpolated polynomial at `Gf256::zero()` where -/// the polynomial is determined using Lagrangian interpolation -/// based on the given `points` in the G(2^8) Galois field. -pub(crate) fn interpolate_at(points: &[(u8, u8)]) -> u8 { - let mut sum = Gf256::zero(); - for (i, &(raw_xi, raw_yi)) in points.iter().enumerate() { - let xi = Gf256::from_byte(raw_xi); - let yi = Gf256::from_byte(raw_yi); - let mut prod = Gf256::one(); - for (j, &(raw_xj, _)) in points.iter().enumerate() { - if i != j { - let xj = Gf256::from_byte(raw_xj); - let delta = xi - xj; - assert_ne!(delta.poly, 0, "Duplicate shares"); - prod *= xj / delta; +/// Stores the intermediate state of interpolation and evaluation at `Gf256::zero()` of a +/// polynomial. A secret may be computed incrementally using barycentric Lagrange interpolation. +pub struct BarycentricWeights { + /// The number of shares necessary to recover the secret, a.k.a. the threshold. + pub diffs: Vec, + /// The barycentric weights. + pub weights: Vec, +} + +// `BarycentricWeights` is not a public-facing struct. We expect the functions that interact with +// it to do validation of its operands (namely, the methods of `sss::Recover`.) Still, we have +// included many assertions to guard against clearly wrong inputs. +impl BarycentricWeights { + /// Create a new partial computation given a `threshold` (to know when the computation is + /// finished), and an initial set of `points`. + #[inline] + pub fn new(ids: &[Gf256], new_ys: &[Gf256]) -> Self { + let (new_points, total_points) = (new_ys.len(), ids.len()); + assert_ne!(new_points, 0, "Given an empty set of points!"); + assert_eq!( + new_points, total_points, + "Given an unequal number of x and y coordinates!" + ); + + let mut partial_comp = Self { + diffs: Vec::with_capacity(total_points), + weights: vec![], + }; + + partial_comp.update_diffs(ids, new_ys); + partial_comp.update_barycentric_weights(ids); + partial_comp + } + + /// Update the partial computation given an additional set of `points`. + #[inline] + pub fn update(&mut self, ids: &[Gf256], new_ys: &[Gf256]) { + let (new_points, total_points) = (new_ys.len(), ids.len()); + assert_ne!(new_points, 0, "Given an empty set of points!"); + assert!(total_points > 1, "In order to call update you must have already processed at least + one point to make a `BarycentricWeights`, and you must provide at least a second in your + call to update."); + assert!( + total_points > new_points, + "During an update IDs of the shares processed should at least number the new y values." + ); + // We use diffs here and not weights because no weights are generated until at least 2 + // points have been interpolated. + assert_eq!(new_points + self.diffs.len(), total_points, "The new points given plus the + existing diffs should be equal in length to the total points given."); + + self.update_diffs(ids, new_ys); + self.update_barycentric_weights(ids); + } + + /// Parse the `new_ys` into `diffs`. + #[inline] + fn update_diffs(&mut self, ids: &[Gf256], new_ys: &[Gf256]) { + let (new_points, total_points) = (new_ys.len(), ids.len()); + let ids = &ids[(total_points - new_points)..]; + self.diffs.reserve_exact(new_points); + + for (&xi, &yi) in ids.iter().zip(new_ys.iter()) { + assert!(xi.poly != 0, "Given invalid share identifier 0!"); + // Storing these `diffs` instead of the `y` values allows us to do a little more + // precomputation, since we really only need `y / x` and not `y` to evaluate the second + // form of the barycentric interpolation formula. + self.diffs.push(yi / xi); + } + } + + /// Update the barycentric weights `w` corresponding to a set of `x` values. + #[inline] + fn update_barycentric_weights(&mut self, ids: &[Gf256]) { + let total_points = ids.len(); + // Need at least two points to start computing the barycentric weights. + if total_points == 1 { + return; + } + let new_points = total_points - self.weights.len(); + + let start_weight = if self.weights.is_empty() { + // Initialize initial weights. + self.weights = vec![Gf256::zero(); total_points]; + self.weights[0] = Gf256::one(); + 1 + } else { + // Initialize additional weights. + self.weights.append(&mut vec![Gf256::zero(); new_points]); + total_points - new_points + }; + + // Update weights using algorithm (3.1) from "Polynomial Interpolation: Langrange vs + // Newton" by Wilhelm Werner. + for i in start_weight..total_points { + for j in 0..i { + let diff = ids[j] - ids[i]; + assert!(diff.poly != 0, "Duplicate share identifiers encountered!"); + self.weights[j] /= diff; + self.weights[i] -= self.weights[j]; } } - sum += prod * yi; } - sum.to_byte() } -/// Computeds the coefficient of the Lagrange polynomial interpolated -/// from the given `points`, in the G(2^8) Galois field. +/// Compute the secret using the second or "true" form of the barycentric interpolation formula +/// at `Gf256::zero()`. +#[inline] +pub fn evaluate_at_zero(wds: &BarycentricWeights, ids: &[Gf256]) -> u8 { + validate_evaluation_parameters(wds, ids); + + let (mut num, mut denom) = (Gf256::zero(), Gf256::zero()); + for ((&xi, &di), &wi) in ids.iter().zip(wds.diffs.iter()).zip(wds.weights.iter()) { + num += wi * di; + denom += wi / xi; + } + + (num / denom).to_byte() +} + +/// Evaluate the interpolated polynomial at the point `gf256!(x)` in the G(2^8) +/// Galois field. +#[inline] +fn evaluate_at_x(wds: &BarycentricWeights, ids: &[Gf256], x: Gf256) -> Result { + validate_evaluation_parameters(wds, ids); + + let (mut num, mut denom) = (Gf256::zero(), Gf256::zero()); + for ((&xi, &di), &wi) in ids.iter().zip(wds.diffs.iter()).zip(wds.weights.iter()) { + let delta = x - xi; + // Slightly slower to re-multiply the `diffs` by `xi` here, but otherwise we have to + // additionally store the `y` values in `BarycentricWeights`, or store `y` values instead + // of the `diffs` and precompute less in the standard case of evaluating at 0. + num += wi * di * xi / delta; + denom += wi / delta; + } + Ok((num / denom).to_byte()) +} + +/// Validates the `BarycentricWeights` and `ids` can successfully be used to compute a secret byte. +#[inline] +fn validate_evaluation_parameters(wds: &BarycentricWeights, ids: &[Gf256]) { + let num_weights = wds.weights.len(); + let num_diffs = wds.diffs.len(); + assert_eq!( + num_weights, num_diffs, + "`BarycentricWeights` should contain the same number of weights and diffs!" + ); + let num_points = ids.len(); + assert_eq!( + num_weights, num_points, + "The number of barycentric weights is not equal to the number of IDs!" + ); + assert!( + num_points >= 2, + "Can't evaluate a polynomial at 0 without at least having processed two points." + ); +} + +/// Computes the coefficient of the Lagrange polynomial interpolated from the given `points`, in +//the G(2^8) Galois field. +#[inline] pub(crate) fn interpolate(points: &[(Gf256, Gf256)]) -> Poly { let len = points.len(); let mut poly = vec![Gf256::zero(); len]; for &(x, y) in points { + assert_ne!(x.poly, 0, "Invalid share x = 0"); let mut coeffs = vec![Gf256::zero(); len]; coeffs[0] = y; @@ -64,31 +205,22 @@ pub(crate) fn interpolate(points: &[(Gf256, Gf256)]) -> Poly { #[allow(trivial_casts)] mod tests { - use std; use super::*; use gf256::*; use quickcheck::*; + use std::u8; quickcheck! { - fn evaluate_at_works(ys: Vec) -> TestResult { - if ys.is_empty() || ys.len() > std::u8::MAX as usize { - return TestResult::discard(); - } - - let points = ys.iter().enumerate().map(|(x, y)| (x as u8, *y)).collect::>(); - let equals = interpolate_at(points.as_slice()) == ys[0]; - - TestResult::from_bool(equals) - } - - fn interpolate_evaluate_at_works(ys: Vec) -> TestResult { - if ys.is_empty() || ys.len() > std::u8::MAX as usize { + if ys.len() < 2 || ys.len() > u8::MAX as usize { return TestResult::discard(); } - let points = ys.into_iter().enumerate().map(|(x, y)| (gf256!(x as u8), y)).collect::>(); + let points = ys.into_iter() + .zip(1..u8::MAX) + .map(|(y, x)| (gf256!(x), y)) + .collect::>(); let poly = interpolate(&points); for (x, y) in points { @@ -101,22 +233,27 @@ mod tests { } fn interpolate_evaluate_at_0_eq_evaluate_at(ys: Vec) -> TestResult { - if ys.len() > std::u8::MAX as usize { + if ys.len() < 2 || ys.len() > u8::MAX as usize { return TestResult::discard(); } - let points = ys.into_iter().enumerate().map(|(x, y)| (x as u8, y)).collect::>(); + // Safe to cast because if `ys.len() > 255` it is discarded. + let num_points = ys.len() as u8; + let ids: Vec = (1..(num_points + 1)).map(|x| gf256!(x)).collect(); + let ys: Vec = ys.iter().map(|&y| gf256!(y)).collect(); - let elems = points - .iter() - .map(|&(x, y)| (gf256!(x), gf256!(y))) - .collect::>(); + let elems: Vec<(Gf256, Gf256)> = ids.iter() + .zip(ys.iter()) + .map(|(&x, &y)| (x, y)) + .collect(); let poly = interpolate(&elems); + let result_poly = poly.evaluate_at(Gf256::zero()).to_byte(); - let equals = poly.evaluate_at(Gf256::zero()).to_byte() == interpolate_at(points.as_slice()); + let wds = BarycentricWeights::new(&ids, &ys); + let result_interpolate = evaluate_at_zero(&wds, &ids); - TestResult::from_bool(equals) + TestResult::from_bool(result_poly == result_interpolate) } } diff --git a/src/lib.rs b/src/lib.rs index 4b884284..90d5c3c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,15 +18,15 @@ extern crate ring; #[macro_use] mod gf256; -mod share; -mod poly; mod lagrange; +mod poly; +mod share; mod vol_hash; pub mod errors; +pub mod proto; pub mod sss; pub mod wrapped_secrets; -pub mod proto; #[cfg(feature = "dss")] pub mod dss; diff --git a/src/share/mod.rs b/src/share/mod.rs index beb1c5c8..7f3e8868 100644 --- a/src/share/mod.rs +++ b/src/share/mod.rs @@ -34,7 +34,8 @@ pub(crate) trait IsSignedShare: IsShare { /// Return the signature itself. fn get_signature(&self) -> &Self::Signature; - /// Verify the signatures of the given batch of shares. - /// Returns `Ok(())` if validation succeeds, and an `Err` otherwise. - fn verify_signatures(shares: &[Self]) -> Result<()>; + /// Verify a given batch of shares are all signed by the same root hash, optionally specifying + /// which one using the `root_hash` argument. Returns the root hash if verification succeeds, + /// and an `Err` otherwise. + fn verify_signatures(shares: &[Self], root_hash: Option<&[u8]>) -> Result>; } diff --git a/src/share/validation.rs b/src/share/validation.rs index f8f94adb..7dd31057 100644 --- a/src/share/validation.rs +++ b/src/share/validation.rs @@ -1,5 +1,3 @@ -use std::collections::{HashMap, HashSet}; - use errors::*; use share::{IsShare, IsSignedShare}; @@ -8,91 +6,120 @@ use share::{IsShare, IsSignedShare}; // 2) Validate duplicate shares share num && data // 2) Validate group consistency // 3) Validate other properties, in no specific order - -/// TODO: Doc -pub(crate) fn validate_signed_shares( - shares: Vec, +pub(crate) fn validate_initial_signed_shares( + shares: &[S], verify_signatures: bool, -) -> Result<(u8, Vec)> { - let (threshold, shares) = validate_shares(shares)?; - +) -> Result<(u8, usize, Option>)> { if verify_signatures { - S::verify_signatures(&shares)?; + let root_hash = vec![]; + validate_additional_signed_shares(shares, None, None, None, Some(&root_hash)) + } else { + validate_additional_signed_shares(shares, None, None, None, None) + } +} + +pub(crate) fn validate_additional_signed_shares( + shares: &[S], + threshold: Option, + slen: Option, + already_verified_ids: Option<&[u8]>, + root_hash: Option<&[u8]>, +) -> Result<(u8, usize, Option>)> { + let (threshold, slen) = validate_shares(shares, threshold, slen, already_verified_ids)?; + + let root_hash = if root_hash.is_some() { + Some(S::verify_signatures(shares, root_hash)?) + } else { + None + }; + + Ok((threshold, slen, root_hash)) +} + +/// Does check there at at least threshold shares. +pub(crate) fn validate_all_signed_shares( + shares: &[S], + verify_signature: bool, +) -> Result<(u8, usize)> { + let result = validate_all_shares(shares)?; + + if verify_signature { + S::verify_signatures(shares, None)?; + } + + Ok(result) +} + +/// Does check there at at least threshold shares. +pub(crate) fn validate_all_shares(shares: &[S]) -> Result<(u8, usize)> { + let (threshold, slen) = validate_shares(shares, None, None, None)?; + + // Safe to cast because `validate_shares` ensures `len() < 255`. + let shares_count = shares.len() as u8; + if shares_count < threshold { + bail!(ErrorKind::MissingShares(shares_count, threshold)) } - Ok((threshold, shares)) + Ok((threshold, slen)) } -/// TODO: Doc -pub(crate) fn validate_shares(shares: Vec) -> Result<(u8, Vec)> { +/// Does not check there at at least threshold shares. +pub(crate) fn validate_shares( + shares: &[S], + threshold: Option, + slen: Option, + already_verified_ids: Option<&[u8]>, +) -> Result<(u8, usize)> { if shares.is_empty() { bail!(ErrorKind::EmptyShares); } let shares_count = shares.len(); - let mut result: Vec = Vec::with_capacity(shares_count); - - let mut k_compatibility_sets = HashMap::new(); + let mut ids = if already_verified_ids.is_some() { + let mut ids = already_verified_ids.unwrap().to_vec(); + ids.reserve_exact(shares_count); + ids + } else { + Vec::with_capacity(shares_count) + }; + // Safe to index since we already confirmed `shares` is nonempty. + let threshold = threshold.unwrap_or_else(|| shares[0].get_threshold()); + let slen = slen.unwrap_or_else(|| shares[0].get_data().len()); for share in shares { - let (id, threshold) = (share.get_id(), share.get_threshold()); - - if id > MAX_SHARES { - bail!(ErrorKind::ShareIdentifierTooBig(id, MAX_SHARES)) - } - + let id = share.get_id(); + let threshold_ = share.get_threshold(); + let slen_ = share.get_data().len(); + + // Public-facing `Share::share_from_string` performs the following three tests, so they are + // redudant in production for the time being. However, for our tests, not all shares tested + // come from strings (e.g., `dss::ss1::Share` we test by constructing from parts), so they + // should be left for now. if id < 1 { bail!(ErrorKind::ShareParsingInvalidShareId(id)) - } - - k_compatibility_sets - .entry(threshold) - .or_insert_with(HashSet::new); - let k_set = k_compatibility_sets.get_mut(&threshold).unwrap(); - k_set.insert(id); - - if result.iter().any(|s| s.get_id() == id) { - bail!(ErrorKind::DuplicateShareId(id)); - } - - if share.get_data().is_empty() { + } else if threshold_ < 2 { + bail!(ErrorKind::ShareParsingInvalidShareThreshold(threshold, id)) + } else if slen_ < 1 { bail!(ErrorKind::ShareParsingErrorEmptyShare(id)) } - if result.iter().any(|s| s.get_data() == share.get_data()) && share.get_threshold() != 1 { - // When threshold = 1, shares data can be the same - bail!(ErrorKind::DuplicateShareData(id)); - } - - result.push(share); - } - - // Validate threshold - let k_sets = k_compatibility_sets.keys().count(); - - match k_sets { - 0 => bail!(ErrorKind::EmptyShares), - 1 => {} // All shares have the same roothash. - _ => { - bail! { - ErrorKind::IncompatibleSets( - k_compatibility_sets - .values() - .map(|x| x.to_owned()) - .collect(), - ) - } + if ids.iter().any(|&x| x == id) { + bail!(ErrorKind::DuplicateShareId(id)); + } else if threshold_ != threshold { + bail!(ErrorKind::InconsistentThresholds( + id, + threshold_, + ids, + threshold + )) + } else if slen_ != slen { + bail!(ErrorKind::InconsistentSecretLengths(id, slen_, ids, slen)) } - } - - // It is safe to unwrap because k_sets == 1 - let threshold = k_compatibility_sets.keys().last().unwrap().to_owned(); - if shares_count < threshold as usize { - bail!(ErrorKind::MissingShares(threshold as usize, shares_count)); + ids.push(id); } - Ok((threshold, result)) + Ok((threshold, slen)) } pub(crate) fn validate_share_count(threshold: u8, shares_count: u8) -> Result<(u8, u8)> { diff --git a/src/sss/encode.rs b/src/sss/encode.rs index d2729fb4..abfe5494 100644 --- a/src/sss/encode.rs +++ b/src/sss/encode.rs @@ -2,17 +2,15 @@ use gf256::Gf256; use std::io; use std::io::prelude::*; -/// evaluates a polynomial at x=1, 2, 3, ... n (inclusive) +/// Evaluates a polynomial at x=1, 2, 3, ... n (inclusive) using +/// Horner's method. pub(crate) fn encode_secret_byte(src: &[u8], n: u8, w: &mut W) -> io::Result<()> { for raw_x in 1..(u16::from(n) + 1) { let x = Gf256::from_byte(raw_x as u8); - let mut fac = Gf256::one(); - let mut acc = Gf256::zero(); - for &coeff in src.iter() { - acc += fac * Gf256::from_byte(coeff); - fac *= x; - } - w.write_all(&[acc.to_byte()])?; + let sum = src.iter().rev().fold(Gf256::zero(), |acc, &coeff| { + Gf256::from_byte(coeff) + acc * x + }); + w.write_all(&[sum.to_byte()])?; } Ok(()) } diff --git a/src/sss/format.rs b/src/sss/format.rs index 0e7c3eae..e6cb2a7e 100644 --- a/src/sss/format.rs +++ b/src/sss/format.rs @@ -1,9 +1,9 @@ +use base64; use errors::*; use merkle_sigs::{MerklePublicKey, Proof, PublicKey}; +use proto::wrapped::ShareProto; use protobuf::{self, Message, RepeatedField}; -use base64; use sss::{Share, HASH_ALGO}; -use proto::wrapped::ShareProto; use std::error::Error; const BASE64_CONFIG: base64::Config = base64::STANDARD_NO_PAD; @@ -49,12 +49,12 @@ pub(crate) fn share_from_string(s: &str, is_signed: bool) -> Result { (k, i, p3) }; - if k < 1 || i < 1 { - bail! { - ErrorKind::ShareParsingError( - format!("Found illegal share info: threshold = {}, identifier = {}.", k, i), - ) - } + if i < 1 { + bail!(ErrorKind::ShareParsingInvalidShareId(i)) + } else if k < 2 { + bail!(ErrorKind::ShareParsingInvalidShareThreshold(k, i)) + } else if p3.is_empty() { + bail!(ErrorKind::ShareParsingErrorEmptyShare(i)) } let raw_data = base64::decode_config(p3, BASE64_CONFIG).chain_err(|| { diff --git a/src/sss/mod.rs b/src/sss/mod.rs index 7709d1b1..667380e4 100644 --- a/src/sss/mod.rs +++ b/src/sss/mod.rs @@ -8,8 +8,8 @@ pub(crate) use self::share::*; mod format; // pub use self::format::*; -mod scheme; -pub(crate) use self::scheme::*; +pub(crate) mod scheme; +use self::scheme::Recover; mod encode; @@ -36,8 +36,7 @@ static HASH_ALGO: &'static Algorithm = &SHA512; /// } /// ``` pub fn split_secret(k: u8, n: u8, secret: &[u8], sign_shares: bool) -> Result> { - SSS::default() - .split_secret(k, n, secret, sign_shares) + scheme::split_secret(k, n, secret, sign_shares) .map(|shares| shares.into_iter().map(Share::into_string).collect()) } @@ -65,5 +64,5 @@ pub fn split_secret(k: u8, n: u8, secret: &[u8], sign_shares: bool) -> Result Result> { let shares = Share::parse_all(shares, verify_signatures)?; - SSS::recover_secret(shares, verify_signatures) + Recover::recover_secret(&shares, verify_signatures) } diff --git a/src/sss/scheme.rs b/src/sss/scheme.rs index acb3723d..fc1032a1 100644 --- a/src/sss/scheme.rs +++ b/src/sss/scheme.rs @@ -1,109 +1,217 @@ -//! SSS provides Shamir's secret sharing with raw data. +//! Provides Shamir's secret sharing with raw data. + +use std::cmp::min; -use rand::{OsRng, Rng}; use merkle_sigs::sign_data_vec; +use rand::{OsRng, Rng}; use errors::*; -use sss::{Share, HASH_ALGO}; +use gf256::Gf256; +use lagrange::{evaluate_at_zero, BarycentricWeights}; +use share::validation::*; use sss::format::format_share_for_signing; -use share::validation::{validate_share_count, validate_signed_shares}; -use lagrange::interpolate_at; +use sss::{Share, HASH_ALGO}; use super::encode::encode_secret_byte; -/// SSS provides Shamir's secret sharing with raw data. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct SSS; - -impl SSS { - /// Performs threshold k-out-of-n Shamir's secret sharing. - pub fn split_secret( - &self, - threshold: u8, - shares_count: u8, - secret: &[u8], - sign_shares: bool, - ) -> Result> { - let (threshold, shares_count) = validate_share_count(threshold, shares_count)?; - let shares = Self::secret_share(secret, threshold, shares_count)?; - - let signatures = if sign_shares { - let shares_to_sign = shares - .iter() - .enumerate() - .map(|(i, x)| format_share_for_signing(threshold, (i + 1) as u8, x)) - .collect::>(); +/// Performs threshold k-out-of-n Shamir's secret sharing. +pub(crate) fn split_secret( + threshold: u8, + shares_count: u8, + secret: &[u8], + sign_shares: bool, +) -> Result> { + let (threshold, shares_count) = validate_share_count(threshold, shares_count)?; + let shares = secret_share(secret, threshold, shares_count)?; + + let signatures = if sign_shares { + let shares_to_sign = shares + .iter() + .enumerate() + .map(|(i, x)| format_share_for_signing(threshold, (i + 1) as u8, x)) + .collect::>(); + + let sign = sign_data_vec(&shares_to_sign, HASH_ALGO) + .unwrap() + .into_iter() + .map(Some) + .collect::>(); + + Some(sign) + } else { + None + }; + + let sig_pairs = signatures + .unwrap_or_else(|| vec![None; shares_count as usize]) + .into_iter() + .map(|sig_pair| sig_pair.map(From::from)); + + let shares_and_sigs = shares.into_iter().enumerate().zip(sig_pairs); + + let result = shares_and_sigs.map(|((index, data), signature_pair)| { + // This is actually safe since we alwaays generate less than 256 shares. + let id = (index + 1) as u8; + + Share { + id, + threshold, + data, + signature_pair, + } + }); - let sign = sign_data_vec(&shares_to_sign, HASH_ALGO) - .unwrap() - .into_iter() - .map(Some) - .collect::>(); + Ok(result.collect()) +} - Some(sign) - } else { - None - }; +fn secret_share(src: &[u8], threshold: u8, shares_count: u8) -> Result>> { + let mut result = Vec::with_capacity(shares_count as usize); + for _ in 0..(shares_count as usize) { + result.push(vec![0u8; src.len()]); + } + let mut col_in = vec![0u8; threshold as usize]; + let mut col_out = Vec::with_capacity(shares_count as usize); + let mut osrng = OsRng::new()?; + for (c, &s) in src.iter().enumerate() { + col_in[0] = s; + // NOTE: switch to `try_fill_bytes` when it lands in a stable release: + // https://github.com/rust-lang-nursery/rand/commit/230b2258dbd99ff8bd991008c972d923d4b5d10c + osrng.fill_bytes(&mut col_in[1..]); + col_out.clear(); + encode_secret_byte(&*col_in, shares_count, &mut col_out)?; + for (&y, share) in col_out.iter().zip(result.iter_mut()) { + share[c] = y; + } + } + Ok(result) +} - let sig_pairs = signatures - .unwrap_or_else(|| vec![None; shares_count as usize]) - .into_iter() - .map(|sig_pair| sig_pair.map(From::from)); +/// `Recover` provides an interface for recovering a secret. +pub(crate) struct Recover { + /// The state of each partially-recovered secret byte. + barycentric: Vec, + /// The ids of the share (varies between 1 and n where n is the total number of generated + /// shares). + ids: Vec, + /// The number of shares necessary to recover the secret. + threshold: u8, + /// The length of the secret. + slen: usize, + /// If the shares are signed, the root hash of the Merkle tree all shares are signed with. + root_hash: Option>, + /// The secret. `None` until computation is complete. + secret: Option>, +} - let shares_and_sigs = shares.into_iter().enumerate().zip(sig_pairs); +impl Recover { + /// Recovers the secret from a k-out-of-n Shamir's secret sharing. + /// + /// At least `k` distinct shares need to be provided to recover the share. + pub fn recover_secret(shares: &[Share], verify_signatures: bool) -> Result> { + let recovery = Self::new(shares, verify_signatures)?; + recovery.get_secret() + } - let result = shares_and_sigs.map(|((index, data), signature_pair)| { - // This is actually safe since we alwaays generate less than 256 shares. - let id = (index + 1) as u8; + /// Begins a partial secret recovery. + pub fn new(shares: &[Share], verify_signatures: bool) -> Result { + let (threshold, slen, root_hash) = + validate_initial_signed_shares(shares, verify_signatures)?; + + let mut incremental_recovery = Self { + barycentric: Vec::with_capacity(slen), + ids: Vec::with_capacity(threshold as usize), + threshold, + slen, + root_hash, + secret: None, + }; - Share { - id, - threshold, - data, - signature_pair, - } - }); + incremental_recovery.process_shares(shares); - Ok(result.collect()) + Ok(incremental_recovery) } - fn secret_share(src: &[u8], threshold: u8, shares_count: u8) -> Result>> { - let mut result = Vec::with_capacity(shares_count as usize); - for _ in 0..(shares_count as usize) { - result.push(vec![0u8; src.len()]); - } - let mut col_in = vec![0u8; threshold as usize]; - let mut col_out = Vec::with_capacity(shares_count as usize); - let mut osrng = OsRng::new()?; - for (c, &s) in src.iter().enumerate() { - col_in[0] = s; - osrng.fill_bytes(&mut col_in[1..]); - col_out.clear(); - encode_secret_byte(&*col_in, shares_count, &mut col_out)?; - for (&y, share) in col_out.iter().zip(result.iter_mut()) { - share[c] = y; - } + /// Contines a partial secret recovery. + pub fn update(&mut self, shares: &[Share]) -> Result<()> { + if self.shares_needed() == 0 { + bail!(ErrorKind::NoMoreSharesNeeded(self.threshold)) } - Ok(result) + + let ids: Vec = self.ids.iter().map(|id| id.poly).collect(); + validate_additional_signed_shares( + shares, + Some(self.threshold), + Some(self.slen), + Some(&ids), + Some(&self.root_hash.clone().unwrap()), + )?; + + self.process_shares(shares); + Ok(()) } - /// Recovers the secret from a k-out-of-n Shamir's secret sharing. - /// - /// At least `k` distinct shares need to be provided to recover the share. - pub fn recover_secret(shares: Vec, verify_signatures: bool) -> Result> { - let (threshold, shares) = validate_signed_shares(shares, verify_signatures)?; - - let slen = shares[0].data.len(); - let mut col_in = Vec::with_capacity(threshold as usize); - let mut secret = Vec::with_capacity(slen); - for byteindex in 0..slen { - col_in.clear(); - for s in shares.iter().take(threshold as usize) { - col_in.push((s.id, s.data[byteindex])); + fn process_shares(&mut self, shares: &[Share]) { + // Don't bother interpolating more than k shares. + let ub = min(self.shares_needed() as usize, shares.len()); + let shares = &shares[..ub]; + let is_new_computation = self.shares_interpolated() == 0; + // NOTE: `shares_interpolated` will be very temporarily be out of sync until the next for + // loop completes. + self.ids.extend(shares.iter().map(|s| gf256!(s.id))); + + for byteindex in 0..self.slen { + let ys: Vec = shares.iter().map(|s| gf256!(s.data[byteindex])).collect(); + if is_new_computation { + self.barycentric + .push(BarycentricWeights::new(&self.ids, &ys)); + } else { + self.barycentric[byteindex].update(&self.ids, &ys); } - secret.push(interpolate_at(&*col_in)); } - Ok(secret) + // If we have sufficient information, we can compute the secret. + if self.shares_needed() == 0 { + self.compute_secret(); + } + } + + /// Computes the secret (called automatically when sufficient shares have been processed). + fn compute_secret(&mut self) { + self.secret = Some( + self.barycentric + .iter() + .map(|wds| evaluate_at_zero(wds, &self.ids)) + .collect(), + ); + } + + /// Used to determine how many more shares are needed to finish computing a partial secret. + pub fn shares_interpolated(&self) -> u8 { + // Safe cast because validation ensures `self.ids.len() < 255`. + self.ids.len() as u8 + } + + /// Returns the threshold of shares needed to recover the secret. + pub fn get_threshold(&self) -> u8 { + self.threshold + } + + /// Used to determine how many more shares are needed to finish computing a partial secret. + pub fn shares_needed(&self) -> u8 { + // Safe unsigned subraction because `process_shares` ensures we never interpolate more than + // `threshold`. + self.threshold - self.shares_interpolated() + } + + /// Used to obtain the resulting secret when `threshold` shares have been evaluated. + pub fn get_secret(&self) -> Result> { + if self.secret.is_some() { + Ok(self.secret.clone().unwrap()) + } else { + bail!(ErrorKind::MissingShares( + self.shares_interpolated(), + self.threshold + )) + } } } diff --git a/src/sss/share.rs b/src/sss/share.rs index f46ad678..23849ea1 100644 --- a/src/sss/share.rs +++ b/src/sss/share.rs @@ -1,8 +1,7 @@ use std::error::Error; -use std::collections::{HashMap, HashSet}; -use merkle_sigs::{MerklePublicKey, Proof}; use merkle_sigs::verify_data_vec_signature; +use merkle_sigs::{MerklePublicKey, Proof}; use errors::*; use share::{IsShare, IsSignedShare}; @@ -87,51 +86,37 @@ impl IsShare for Share { impl IsSignedShare for Share { type Signature = Option; - fn verify_signatures(shares: &[Self]) -> Result<()> { - let mut rh_compatibility_sets = HashMap::new(); + fn verify_signatures(shares: &[Self], root_hash: Option<&[u8]>) -> Result> { + let mut root_hash = root_hash.unwrap_or(&[]).to_vec(); + let mut ids = Vec::with_capacity(shares.len()); for share in shares { + let id = share.get_id(); if !share.is_signed() { - bail!(ErrorKind::MissingSignature(share.get_id())); + bail!(ErrorKind::MissingSignature(id)); } let sig_pair = share.signature_pair.as_ref().unwrap(); let signature = &sig_pair.signature; let proof = &sig_pair.proof; - let root_hash = &proof.root_hash; + let root_hash_ = &proof.root_hash; verify_data_vec_signature( - format_share_for_signing(share.threshold, share.id, share.data.as_slice()), + format_share_for_signing(share.threshold, id, &share.data), &(signature.to_vec(), proof.clone()), - root_hash, + root_hash_, ).map_err(|e| ErrorKind::InvalidSignature(share.id, String::from(e.description())))?; - rh_compatibility_sets - .entry(root_hash) - .or_insert_with(HashSet::new); - - let rh_set = rh_compatibility_sets.get_mut(&root_hash).unwrap(); - rh_set.insert(share.id); - } - - let rh_sets = rh_compatibility_sets.keys().count(); - - match rh_sets { - 0 => bail!(ErrorKind::EmptyShares), - 1 => {} // All shares have the same roothash. - _ => { - bail! { - ErrorKind::IncompatibleSets( - rh_compatibility_sets - .values() - .map(|x| x.to_owned()) - .collect(), - ) - } + if root_hash.is_empty() { + root_hash = root_hash_.clone(); + } else if *root_hash_ != root_hash { + bail!(ErrorKind::InconsistentSignatures(id, ids)) } + + ids.push(id); } - Ok(()) + Ok(root_hash) } fn is_signed(&self) -> bool { diff --git a/src/wrapped_secrets/scheme.rs b/src/wrapped_secrets/scheme.rs index 40c50f86..6e204f2c 100644 --- a/src/wrapped_secrets/scheme.rs +++ b/src/wrapped_secrets/scheme.rs @@ -1,11 +1,11 @@ use errors::*; +use proto::VersionProto; +use proto::wrapped::SecretProto; use protobuf; use protobuf::Message; -use proto::wrapped::SecretProto; -use proto::VersionProto; -use sss::SSS; pub(crate) use sss::Share; +use sss::scheme::*; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct WrappedSecrets; @@ -30,14 +30,14 @@ impl WrappedSecrets { let data = rusty_secret.write_to_bytes().unwrap(); - SSS::default().split_secret(k, n, data.as_slice(), sign_shares) + split_secret(k, n, data.as_slice(), sign_shares) } /// Recovers the secret from a k-out-of-n Shamir's secret sharing. /// /// At least `k` distinct shares need to be provided to recover the share. pub fn recover_secret(shares: Vec, verify_signatures: bool) -> Result { - let secret = SSS::recover_secret(shares, verify_signatures)?; + let secret = Recover::recover_secret(&shares, verify_signatures)?; protobuf::parse_from_bytes::(secret.as_slice()) .chain_err(|| ErrorKind::SecretDeserializationError) diff --git a/tests/recovery_errors.rs b/tests/recovery_errors.rs index 7a36dbe0..0e1e8f5b 100644 --- a/tests/recovery_errors.rs +++ b/tests/recovery_errors.rs @@ -11,6 +11,13 @@ fn test_recover_no_shares() { } } +#[test] +#[should_panic(expected = "ShareParsingErrorEmptyShare")] +fn test_share_parsing_error_empty_share() { + let shares = vec!["2-1-".to_string()]; + recover_secret(&shares, false).unwrap(); +} + #[test] #[should_panic(expected = "ShareParsingError")] fn test_recover_2_parts_share() { @@ -34,13 +41,9 @@ fn test_recover_incorrect_share_num() { } #[test] -#[should_panic(expected = "ShareParsingError")] +#[should_panic(expected = "ShareParsingInvalidShareId")] fn test_recover_0_share_num() { - let share1 = "2-0-1YAYwmOHqZ69jA".to_string(); - let share2 = "2-1-YJZQDGm22Y77Gw".to_string(); - - let shares = vec![share1, share2]; - + let shares = vec!["2-0-1YAYwmOHqZ69jA".to_string()]; recover_secret(&shares, false).unwrap(); } @@ -67,10 +70,21 @@ fn test_recover_duplicate_shares_number() { } #[test] -#[should_panic(expected = "DuplicateShareData")] -fn test_recover_duplicate_shares_data() { +#[should_panic(expected = "InconsistentSecretLengths")] +fn test_recover_inconsistent_secret_lengths() { let share1 = "2-1-CgnlCxRNtnkzENE".to_string(); - let share2 = "2-2-CgnlCxRNtnkzENE".to_string(); + let share2 = "2-2-ChbG46L1zRszs0PPn63XnnupmZTcgYJ3".to_string(); + + let shares = vec![share1, share2]; + + recover_secret(&shares, false).unwrap(); +} + +#[test] +#[should_panic(expected = "InconsistentThresholds")] +fn test_inconsistent_thresholds() { + let share1 = "2-1-CgnlCxRNtnkzENE".to_string(); + let share2 = "3-2-CgkAnUgP3lfwjyM".to_string(); let shares = vec![share1, share2]; @@ -88,6 +102,17 @@ fn test_recover_too_few_shares() { recover_secret(&shares, false).unwrap(); } +#[test] +#[should_panic(expected = "ShareParsingInvalidShareThreshold")] +fn test_recover_invalid_share_threshold() { + let share1 = "1-1-CgnlCxRNtnkzENE".to_string(); + let share2 = "1-1-CgkAnUgP3lfwjyM".to_string(); + + let shares = vec![share1, share2]; + + recover_secret(&shares, false).unwrap(); +} + // See https://github.com/SpinResearch/RustySecrets/issues/43 #[test] fn test_recover_too_few_shares_bug() { diff --git a/tests/ss1_recovery_errors.rs b/tests/ss1_recovery_errors.rs index 4ab71a2c..1ad4d11f 100644 --- a/tests/ss1_recovery_errors.rs +++ b/tests/ss1_recovery_errors.rs @@ -135,32 +135,6 @@ fn test_recover_duplicate_shares_number() { recover_secret(&shares).unwrap(); } -#[test] -#[should_panic(expected = "DuplicateShareData")] -fn test_recover_duplicate_shares_data() { - let hash = get_test_hash(); - let share1 = Share { - id: 1, - threshold: TEST_THRESHOLD, - shares_count: TEST_SHARES_COUNT, - data: "1YAYwmOHqZ69jA".to_string().into_bytes(), - hash: hash.clone(), - metadata: None, - }; - let share2 = Share { - id: 2, - threshold: TEST_THRESHOLD, - shares_count: TEST_SHARES_COUNT, - data: "1YAYwmOHqZ69jA".to_string().into_bytes(), - hash: hash.clone(), - metadata: None, - }; - - let shares = vec![share1, share2]; - - recover_secret(&shares).unwrap(); -} - #[test] #[should_panic(expected = "MissingShares")] fn test_recover_too_few_shares() { diff --git a/tests/test_vectors.rs b/tests/test_vectors.rs index 3be698c7..c5f1f625 100644 --- a/tests/test_vectors.rs +++ b/tests/test_vectors.rs @@ -62,7 +62,7 @@ fn test_recover_es_test_vectors() { } #[test] -fn test_recover_sellibitze_more_than_threshold_shars() { +fn test_recover_sellibitze_more_than_threshold_shares() { let share1 = "2-1-1YAYwmOHqZ69jA"; let share2 = "2-4-F7rAjX3UOa53KA"; let share3 = "2-2-YJZQDGm22Y77Gw"; diff --git a/tests/thss_recovery_errors.rs b/tests/thss_recovery_errors.rs index 4c6c58ae..173680a8 100644 --- a/tests/thss_recovery_errors.rs +++ b/tests/thss_recovery_errors.rs @@ -106,29 +106,6 @@ fn test_recover_duplicate_shares_number() { recover_secret(&shares).unwrap(); } -#[test] -#[should_panic(expected = "DuplicateShareData")] -fn test_recover_duplicate_shares_data() { - let share1 = Share { - id: 1, - threshold: 2, - shares_count: 2, - data: "1YAYwmOHqZ69jA".to_string().into_bytes(), - metadata: None, - }; - let share2 = Share { - id: 2, - threshold: 2, - shares_count: 2, - data: "1YAYwmOHqZ69jA".to_string().into_bytes(), - metadata: None, - }; - - let shares = vec![share1, share2]; - - recover_secret(&shares).unwrap(); -} - #[test] #[should_panic(expected = "MissingShares")] fn test_recover_too_few_shares() {