diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 98fdf54..8feee09 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -115,6 +115,9 @@ jobs: - name: Run tests run: cargo test --verbose + - name: Run tests for share_x + run: cargo test --verbose --no-default-features --features "std, share_x" + test-nostd: name: test runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index e6420ed..a16458d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ default = ["std", "zeroize_memory"] std = ["rand/std", "rand/std_rng"] fuzzing = ["std", "arbitrary"] zeroize_memory = ["zeroize"] +share_x = [] [dependencies] rand = { version = "0.8.5", default-features = false } diff --git a/README.md b/README.md index d2ff5c7..99a25f6 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,22 @@ You can run them with `cargo test` and `cargo bench`. | ------------ | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | | Apple M1 Pro | [2.6976 µs 2.7007 µs 2.7039 µs] | [938.79 ps 939.83 ps 941.04 ps] | [190.00 µs 190.46 µs 191.06 µs] | [31.176 ns 31.311 ns 31.529 ns] | [23.196 ns 23.211 ns 23.230 ns] | +# Roadmap + +- [Barycentric interpolation](https://epubs.siam.org/doi/10.1137/S0036144502417715) +- shares serialization/deserialization +- ssskit-cli +- [Verifiable secret sharing](https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf) + - Next step: [Publicly verifiable secret sharing](https://crypto.ethz.ch/publications/files/Stadle96.pdf) with commitments over EC prime-field group (Ristretto255) +- [Robust secret sharing](https://dl.acm.org/doi/pdf/10.1145/195613.195621) +- [Threshold changeable secret resharing](https://alinush.github.io/2024/04/26/How-to-reshare-a-secret.html) + - [Threshold changeable secret sharing with secure secret reconstruction](https://www.sciencedirect.com/science/article/abs/pii/S0020019020300156) + - [Resharing Shamir Secret Shares to Change the Threshold](https://conduition.io/cryptography/shamir-resharing) +- Arbitrary GF(2^k) support +- Side-channel and constant time primitives. Integrate `subtle` +- Optional SIMD feature for field operations +- Better error handling: `thiserror` integration + # Contributing If you find a vulnerability, bug or would like a new feature, [open a new issue](https://github.com/multifactor/ssskit/issues/new). diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 48aeaf8..e1b26c5 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -21,7 +21,14 @@ fn recover(c: &mut Criterion) { let shares = dealer.take(255).collect::>>(); c.bench_function("recover_secret", |b| { - b.iter(|| sss.recover(black_box(&shares))) + b.iter(|| { + sss.recover(black_box( + &shares + .iter() + .map(|s: &Share| Some(s.clone())) + .collect::>>>(), + )) + }) }); } diff --git a/src/lib.rs b/src/lib.rs index 2b853b5..25b4890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,12 @@ //! # { //! let dealer = sss.dealer(&[1, 2, 3, 4]); //! // Get 10 shares -//! let shares = dealer.take(10).collect::>>(); +//! let shares = dealer +//! .take(10) +//! .map(Some) +//! .collect::>>>(); //! // Recover the original secret! -//! let secret = sss.recover(shares.as_slice()).unwrap(); +//! let secret = sss.recover(&shares).unwrap(); //! assert_eq!(secret, vec![1, 2, 3, 4]); //! # } //! ``` @@ -34,9 +37,12 @@ //! let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); //! let dealer = sss.dealer_rng::(&[1, 2, 3, 4], &mut rng); //! // Get 10 shares -//! let shares = dealer.take(10).collect::>>(); +//! let shares = dealer +//! .take(10) +//! .map(Some) +//! .collect::>>>(); //! // Recover the original secret! -//! let secret = sss.recover(shares.as_slice()).unwrap(); +//! let secret = sss.recover(&shares).unwrap(); //! assert_eq!(secret, vec![1, 2, 3, 4]); //! ``` //! @@ -48,6 +54,38 @@ //! Commonly used polynomials: //! - 0x11B — used in AES (Rijndael) //! - 0x11D — commonly used in Reed–Solomon (e.g., QR codes) +//! +//! # Feature flags and share variants +//! +//! This crate exposes compile-time feature flags to select the share representation and +//! other behavior: +//! +//! - `std` — enables `dealer` convenience (uses `rand::thread_rng`). Without `std`, use `dealer_rng`. +//! - `zeroize_memory` — enables `Zeroize` on share types to clear memory on drop. +//! - default (no `share_x`) — `Share` stores only `y` values. The `x` coordinate is implicit +//! and derived from the iteration order (1-based) when generating or consuming shares. +//! - `share_x` — `Share` stores both `x` and `y`. The `x` is carried with each share. +//! +//! By default, `share_x` is disabled (no-x). To use `share_x`, enable `share_x` explicitly. +//! +//! Example (Cargo.toml): +//! +//! ```toml +//! ssskit = { version = "0.1", default-features = false, features = ["std", "zeroize_memory", "share_x"] } +//! ``` +//! +//! Serialization format: +//! - Default (no x-coordinate): `Vec` representation contains only `y` bytes. +//! - With `share_x`: `Vec` representation is `[x, y...]` (first byte is `x`). +//! +//! API notes: +//! - `recover`: pass an iterator of `Option`; use `Some(share)` for known shares. +//! This supports both variants uniformly (with or without `x`). +//! - `recover_shares`: fill a target of size `n` using `Option` positions (`None` for +//! unknowns). Positions map to indices `1..=n`. +//! +//! In `share_x`, `x` in each `Share` is used directly. Without x-coordinate, the iterator index +//! is used as `x` (1-based) during interpolation and resharing. #![cfg_attr(not(feature = "std"), no_std)] mod field; @@ -63,6 +101,8 @@ use field::GF256; pub use field::PRIMITIVE_POLYS; pub use share::Share; +use crate::share::ShareWithX; + /// 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. /// @@ -77,7 +117,10 @@ pub use share::Share; /// # { /// let dealer = sss.dealer(&[1, 2, 3, 4]); /// // Get 10 shares -/// let shares = dealer.take(10).collect::>>(); +/// let shares = dealer +/// .take(10) +/// .map(Some) +/// .collect::>>>(); /// // Recover the original secret! /// let secret = sss.recover(&shares).unwrap(); /// assert_eq!(secret, vec![1, 2, 3, 4]); @@ -147,7 +190,11 @@ impl SecretSharing { /// # const POLY: u16 = 0x11d_u16; /// # let sss = SecretSharing::(3); /// # let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - /// # let mut shares = sss.dealer_rng::(&[1], &mut rng).take(3).collect::>>(); + /// # let mut shares = sss + /// # .dealer_rng::(&[1], &mut rng) + /// # .take(3) + /// # .map(Some) + /// # .collect::>>>(); /// // Recover original secret from shares /// let mut secret = sss.recover(&shares); /// // Secret correctly recovered @@ -159,14 +206,21 @@ impl SecretSharing { /// assert!(secret.is_err()); pub fn recover<'a, T>(&self, shares: T) -> Result, &str> where - T: IntoIterator>, - T::IntoIter: Iterator>, + T: IntoIterator>>, + T::IntoIter: Iterator>>, { let mut share_length: Option = None; - let mut keys: HashSet = HashSet::new(); - let mut values: Vec> = Vec::new(); + let mut keys: HashSet> = HashSet::new(); + let mut values: Vec> = Vec::new(); + + #[allow(unused_variables)] + for (i, share) in shares.into_iter().enumerate() { + if share.is_none() { + continue; + } + + let share = share.as_ref().unwrap(); - for share in shares.into_iter() { if share_length.is_none() { share_length = Some(share.y.len()); } @@ -174,19 +228,29 @@ impl SecretSharing { if Some(share.y.len()) != share_length { return Err("All shares must have the same length"); } else { - keys.insert(share.x.0); - values.push(share.clone()); + keys.insert(Vec::from(share)); + #[cfg(feature = "share_x")] + { + values.push(share.clone()); + } + #[cfg(not(feature = "share_x"))] + { + values.push(ShareWithX { + x: GF256(i as u8 + 1), + y: share.y.clone(), + }); + } } } if keys.is_empty() || (keys.len() < self.0 as usize) { Err("Not enough shares to recover original secret") } else { - Ok(math::interpolate(values.as_slice())) + Ok(math::interpolate(&values)) } } - /// Given an iterable collection of shares, recovers the original secret. + /// Given an iterable collection of shares (optionally with None for unknown shares), recovers the original shares up to the threshold. /// If the number of distinct shares is less than the minimum threshold an `Err` is returned, /// otherwise an `Ok` containing the desired number of shares. /// @@ -217,11 +281,12 @@ impl SecretSharing { T::IntoIter: Iterator>>, { let mut share_length: Option = None; - let mut keys: HashSet = HashSet::new(); - let mut values: Vec> = Vec::new(); + let mut keys: HashSet> = HashSet::new(); + let mut values: Vec<(GF256, Share)> = Vec::new(); let mut count = 0; - for share in shares.into_iter() { + #[allow(unused_variables)] + for (i, share) in shares.into_iter().enumerate() { if share.is_none() { count += 1; continue; @@ -236,8 +301,15 @@ impl SecretSharing { if Some(share.y.len()) != share_length { return Err("All shares must have the same length"); } else { - keys.insert(share.x.0); - values.push(share.clone()); + keys.insert(Vec::from(share)); + #[cfg(feature = "share_x")] + { + values.push((share.x.clone(), share.clone())); + } + #[cfg(not(feature = "share_x"))] + { + values.push((GF256(i as u8 + 1), share.clone())); + } count += 1; } } @@ -250,7 +322,12 @@ impl SecretSharing { Err("Not enough shares to recover original shares") } else if self.0 == 1 { // if threshold is 1, return the shares as is n times - Ok(values.iter().cloned().cycle().take(n).collect()) + Ok(values + .iter() + .map(|(_, share)| share.clone()) + .cycle() + .take(n) + .collect()) } else { Ok((1..=n).map(|i| math::reshare(&values, i)).collect()) } @@ -262,14 +339,14 @@ mod tests { use super::{SecretSharing, Share}; use alloc::{vec, vec::Vec}; - const POLY: u16 = 0x11d_u16; + const POLY: u16 = 0x11b_u16; impl SecretSharing { #[cfg(not(feature = "std"))] fn make_shares(&self, secret: &[u8]) -> impl Iterator> { - use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; + use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; - let mut rng = ChaCha8Rng::from_seed([0x90; 32]); + let mut rng = ChaCha20Rng::from_seed([10; 32]); self.dealer_rng(secret, &mut rng) } @@ -283,6 +360,7 @@ mod tests { fn test_insufficient_shares_err() { let sss = SecretSharing::(255); let shares: Vec> = sss.make_shares(&[1]).take(254).collect(); + let shares: Vec>> = shares.iter().map(|s| Some(s.clone())).collect(); let secret = sss.recover(&shares); assert!(secret.is_err()); } @@ -291,10 +369,20 @@ mod tests { fn test_duplicate_shares_err() { let sss = SecretSharing::(255); let mut shares: Vec> = sss.make_shares(&[1]).take(255).collect(); - shares[1] = Share { - x: shares[0].x.clone(), - y: shares[0].y.clone(), - }; + #[cfg(not(feature = "share_x"))] + { + shares[1] = Share { + y: shares[0].y.clone(), + }; + } + #[cfg(feature = "share_x")] + { + shares[1] = Share { + x: shares[0].x.clone(), + y: shares[0].y.clone(), + }; + } + let shares: Vec>> = shares.iter().map(|s| Some(s.clone())).collect(); let secret = sss.recover(&shares); assert!(secret.is_err()); } @@ -303,6 +391,7 @@ mod tests { fn test_integration_works() { let sss = SecretSharing::(255); let shares: Vec> = sss.make_shares(&[1, 2, 3, 4]).take(255).collect(); + let shares: Vec>> = shares.iter().map(|s| Some(s.clone())).collect(); let secret = sss.recover(&shares).unwrap(); assert_eq!(secret, vec![1, 2, 3, 4]); } @@ -321,7 +410,11 @@ mod tests { assert_eq!(recovered_shares.len(), 4); for (recovered_share, share) in recovered_shares.iter().zip(shares.iter()) { - assert_eq!(recovered_share.x, share.x); + #[cfg(feature = "share_x")] + { + assert_eq!(recovered_share.x, share.x); + } + assert_eq!(recovered_share.y, share.y); } @@ -334,7 +427,11 @@ mod tests { assert_eq!(recovered_shares.len(), 4); for (recovered_share, share) in recovered_shares.iter().zip(shares.iter()) { - assert_eq!(recovered_share.x, share.x); + #[cfg(feature = "share_x")] + { + assert_eq!(recovered_share.x, share.x); + } + assert_eq!(recovered_share.y, share.y); } @@ -347,7 +444,11 @@ mod tests { assert_eq!(recovered_shares.len(), 4); for (recovered_share, share) in recovered_shares.iter().zip(shares.iter()) { - assert_eq!(recovered_share.x, share.x); + #[cfg(feature = "share_x")] + { + assert_eq!(recovered_share.x, share.x); + } + assert_eq!(recovered_share.y, share.y); } @@ -355,4 +456,60 @@ mod tests { sss.recover_shares([Some(&shares[0]), None, None, Some(&shares[3])], 4); assert!(recovered_shares.is_err()); } + + #[test] + fn test_k_of_n() { + let sharks = SecretSharing::(2); + let shares: Vec> = sharks.make_shares(&[18, 52, 86, 120]).take(4).collect(); + + let recovered_shares = sharks + .recover_shares( + [Some(&shares[0]), Some(&shares[1]), Some(&shares[2]), None], + 4, + ) + .unwrap(); + assert_eq!(recovered_shares.len(), 4); + + for (recovered_share, share) in recovered_shares.iter().zip(shares.iter()) { + #[cfg(feature = "share_x")] + { + assert_eq!(recovered_share.x, share.x); + } + + assert_eq!(recovered_share.y, share.y); + } + } + + #[cfg(feature = "share_x")] + #[test] + fn test_recover_order_independent_with_x() { + let sss = SecretSharing::(3); + let shares: Vec> = sss.make_shares(&[7, 8, 9]).take(5).collect(); + + let shuffled: Vec> = + vec![shares[2].clone(), shares[4].clone(), shares[0].clone()]; + + let shares_opt: Vec>> = shuffled.into_iter().map(Some).collect(); + let secret = sss.recover(&shares_opt).unwrap(); + assert_eq!(secret, vec![7, 8, 9]); + } + + #[test] + fn test_threshold_one_recover_shares() { + let sss = SecretSharing::(1); + let shares: Vec> = sss.make_shares(&[42, 43]).take(1).collect(); + let recovered = sss + .recover_shares([Some(&shares[0]), None, None], 3) + .unwrap(); + assert_eq!(recovered.len(), 3); + for r in &recovered { + assert_eq!(r.y, shares[0].y); + } + #[cfg(feature = "share_x")] + { + for r in &recovered { + assert_eq!(r.x, shares[0].x); + } + } + } } diff --git a/src/math.rs b/src/math.rs index 43193a6..afac3e8 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,18 +1,17 @@ // A module which contains necessary algorithms to compute Shamir's shares and recover secrets +use alloc::vec; use alloc::vec::Vec; -use rand::distributions::Distribution; -use rand::distributions::Uniform; - -use super::field::GF256; -use super::share::Share; +use crate::field::GF256; +use crate::share::Share; +use crate::share::ShareWithX; // Finds the [root of the Lagrange polynomial](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing#Computationally_efficient_approach). // 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: &[Share]) -> Vec { +pub fn interpolate(shares: &[ShareWithX]) -> Vec { (0..shares[0].y.len()) .map(|s| { shares @@ -66,22 +65,25 @@ pub fn interpolate_polynomial( } /// Resharing a share at a given index. -pub fn reshare(shares: &[Share], index: usize) -> Share { +pub fn reshare( + shares: &[(GF256, Share)], + index: usize, +) -> Share { // assert that atleast 2 shares exist assert!( shares.len() >= 2 && shares.len() <= 255, "atleast 2 shares and atmost 255 shares are required" ); - let secret_length = shares[0].y.len(); + let secret_length = shares[0].1.y.len(); let mut new_secret = Vec::new(); for i in 0..secret_length { let mut x_values = Vec::new(); let mut y_values = Vec::new(); for share in shares { - x_values.push(share.x.clone()); - y_values.push(share.y[i].clone()); + x_values.push(share.0.clone()); + y_values.push(share.1.y[i].clone()); } new_secret.push(interpolate_polynomial( &x_values, @@ -90,9 +92,16 @@ pub fn reshare(shares: &[Share], index: usize) -> Share

( ) -> Vec> { let k = k as usize; let mut poly = Vec::with_capacity(k); - let between = Uniform::new_inclusive(1, 255); - for _ in 1..k { - poly.push(GF256(between.sample(rng))); + let mut random_bytes = vec![0u8; k - 1]; + + rng.fill(random_bytes.as_mut_slice()); + for random_byte in random_bytes.iter().rev() { + poly.push(GF256(*random_byte)); } poly.push(s); @@ -122,21 +133,28 @@ pub fn random_polynomial( pub fn get_evaluator( polys: Vec>>, ) -> impl Iterator> { - (1..=u8::MAX).map(GF256).map(move |x| Share { - x: x.clone(), - y: polys + (1..=u8::MAX).map(GF256).map(move |x| { + let y = polys .iter() .map(|p| { p.iter() .fold(GF256(0), |acc, c| acc * x.clone() + c.clone()) }) - .collect(), + .collect(); + #[cfg(feature = "share_x")] + { + Share { x: x.clone(), y } + } + #[cfg(not(feature = "share_x"))] + { + Share { y } + } }) } #[cfg(test)] mod tests { - use super::{get_evaluator, interpolate, random_polynomial, reshare, Share, GF256}; + use super::{get_evaluator, interpolate, random_polynomial, reshare, Share, ShareWithX, GF256}; use alloc::{vec, vec::Vec}; use rand_chacha::rand_core::SeedableRng; use rstest::rstest; @@ -156,11 +174,8 @@ mod tests { #[test] fn evaluator_works() { let iter = get_evaluator::(vec![vec![GF256(3), GF256(2), GF256(5)]]); - let values: Vec<_> = iter.take(2).map(|s| (s.x.clone(), s.y.clone())).collect(); - assert_eq!( - values, - vec![(GF256(1), vec![GF256(4)]), (GF256(2), vec![GF256(13)])] - ); + let values: Vec<_> = iter.take(2).map(|s| s.y.clone()).collect(); + assert_eq!(values, vec![(vec![GF256(4)]), (vec![GF256(13)])]); } #[rstest] @@ -171,7 +186,14 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::from_seed(seed); let poly = random_polynomial(GF256(185), k as u8, &mut rng); let iter = get_evaluator(vec![poly]); - let shares: Vec> = iter.take(k).collect(); + let shares: Vec> = iter + .take(k) + .enumerate() + .map(|(i, s)| ShareWithX { + x: GF256(i as u8 + 1), + y: s.y.clone(), + }) + .collect(); let root = interpolate(&shares); assert_eq!(root, vec![185]); } @@ -185,8 +207,12 @@ mod tests { let mut rng = rand_chacha::ChaCha8Rng::from_seed(seed); let poly = random_polynomial(GF256(185), k as u8, &mut rng); let iter = get_evaluator(vec![poly]); - let shares: Vec> = iter.take(k).collect(); + let shares: Vec<(GF256, Share)> = iter + .take(k) + .enumerate() + .map(|(i, s)| (GF256(i as u8 + 1), s)) + .collect(); let share = reshare(&shares, index); - assert_eq!(share.y, shares[index - 1].y); + assert_eq!(share.y, shares[index - 1].1.y); } } diff --git a/src/share.rs b/src/share.rs index 6563c9b..3ff5b25 100644 --- a/src/share.rs +++ b/src/share.rs @@ -8,6 +8,26 @@ use arbitrary::Arbitrary; #[cfg(feature = "zeroize_memory")] use zeroize::Zeroize; +#[derive(Clone)] +#[cfg_attr(feature = "fuzzing", derive(Arbitrary, Debug))] +#[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] +#[cfg_attr(feature = "zeroize_memory", zeroize(drop))] +pub struct ShareNoX { + /// The y coordinates of the share. + pub y: Vec>, +} + +#[derive(Clone)] +#[cfg_attr(feature = "fuzzing", derive(Arbitrary, Debug))] +#[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] +#[cfg_attr(feature = "zeroize_memory", zeroize(drop))] +pub struct ShareWithX { + /// The x coordinate of the share. + pub x: GF256, + /// The y coordinates of the share. + pub y: Vec>, +} + /// A share used to reconstruct the secret. Can be serialized to and from a byte array. /// /// Usage example: @@ -16,7 +36,7 @@ use zeroize::Zeroize; /// use core::convert::TryFrom; /// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; /// # fn send_to_printer(_: Vec) {} -/// # fn ask_shares() -> Vec> {vec![vec![1, 2], vec![2, 3], vec![3, 4]]} +/// # fn ask_shares() -> Vec> {vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]} /// /// // Transmit the share bytes to a printer /// # const POLY: u16 = 0x11d_u16; @@ -31,20 +51,95 @@ use zeroize::Zeroize; /// /// // Get share bytes from an external source and recover secret /// let shares_bytes: Vec> = ask_shares(); -/// let shares: Vec> = shares_bytes.iter().map(|s| Share::::try_from(s.as_slice()).unwrap()).collect(); +/// let shares: Vec>> = shares_bytes +/// .iter() +/// .map(|s| Some(Share::::try_from(s.as_slice()).unwrap())) +/// .collect(); /// let secret = sss.recover(&shares).unwrap(); -#[derive(Clone)] -#[cfg_attr(feature = "fuzzing", derive(Arbitrary, Debug))] -#[cfg_attr(feature = "zeroize_memory", derive(Zeroize))] -#[cfg_attr(feature = "zeroize_memory", zeroize(drop))] -pub struct Share { - pub x: GF256, - pub y: Vec>, +/// #[cfg(feature = "share_nox")] +/// { +/// assert_eq!(secret, vec![0, 5, 2]); +/// } +/// #[cfg(feature = "share_x")] +/// { +/// assert_eq!(secret, vec![5, 2]); +/// } +/// ``` +/// +/// # Serialization format: +/// - Default (no x-coordinate): `Vec` representation contains only `y` bytes. +/// - With `share_x`: `Vec` representation is `[x, y...]` (first byte is `x`). +#[cfg(not(feature = "share_x"))] +pub type Share = ShareNoX; +/// A share used to reconstruct the secret. Can be serialized to and from a byte array. +/// +/// Usage example (when share_x is enabled): +/// ``` +/// use ssskit::{SecretSharing, Share}; +/// use core::convert::TryFrom; +/// # use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; +/// # fn send_to_printer(_: Vec) {} +/// # fn ask_shares() -> Vec> {vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]} +/// +/// // Transmit the share bytes to a printer +/// # const POLY: u16 = 0x11d_u16; +/// let sss = SecretSharing::(3); +/// let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); +/// let dealer = sss.dealer_rng::(&[1, 2, 3], &mut rng); +/// +/// // Get 5 shares and print paper keys +/// for s in dealer.take(5) { +/// send_to_printer(Vec::from(&s)); +/// }; +/// +/// // Get share bytes from an external source and recover secret +/// let shares_bytes: Vec> = ask_shares(); +/// let shares: Vec>> = shares_bytes +/// .iter() +/// .map(|s| Some(Share::::try_from(s.as_slice()).unwrap())) +/// .collect(); +/// let secret = sss.recover(&shares).unwrap(); +/// #[cfg(feature = "share_x")] +/// { +/// assert_eq!(secret, vec![5, 2]); +/// } +/// #[cfg(feature = "share_nox")] +/// { +/// assert_eq!(secret, vec![0, 5, 2]); +/// } +/// ``` +/// +/// # Serialization format: +/// - Default (no x-coordinate): `Vec` representation contains only `y` bytes. +/// - With `share_x`: `Vec` representation is `[x, y...]` (first byte is `x`). +#[cfg(feature = "share_x")] +pub type Share = ShareWithX; + +/// Converts a ShareNoX to a vector of bytes, where the bytes are the y values. +impl From<&ShareNoX> for Vec { + fn from(s: &ShareNoX) -> Vec { + let mut bytes = Vec::with_capacity(s.y.len()); + bytes.extend(s.y.iter().map(|p| p.0)); + bytes + } } -/// Obtains a byte vector from a `Share` instance -impl From<&Share> for Vec { - fn from(s: &Share) -> Vec { +impl core::convert::TryFrom<&[u8]> for ShareNoX { + type Error = &'static str; + + fn try_from(s: &[u8]) -> Result, Self::Error> { + if s.len() < 2 { + Err("A Share must be at least 2 bytes long") + } else { + let y = s.iter().map(|p| GF256(*p)).collect(); + Ok(ShareNoX { y }) + } + } +} + +/// Converts a ShareWithX to a vector of bytes, where the first byte is the x value and the rest are the y values. +impl From<&ShareWithX> for Vec { + fn from(s: &ShareWithX) -> Vec { let mut bytes = Vec::with_capacity(s.y.len() + 1); bytes.push(s.x.0); bytes.extend(s.y.iter().map(|p| p.0)); @@ -52,17 +147,16 @@ impl From<&Share> for Vec { } } -/// Obtains a `Share` instance from a byte slice -impl core::convert::TryFrom<&[u8]> for Share { +impl core::convert::TryFrom<&[u8]> for ShareWithX { type Error = &'static str; - fn try_from(s: &[u8]) -> Result, Self::Error> { + fn try_from(s: &[u8]) -> Result, Self::Error> { if s.len() < 2 { Err("A Share must be at least 2 bytes long") } else { let x = GF256(s[0]); let y = s[1..].iter().map(|p| GF256(*p)).collect(); - Ok(Share { x, y }) + Ok(ShareWithX { x, y }) } } } @@ -74,6 +168,17 @@ mod tests { use core::convert::TryFrom; const POLY: u16 = 0x11d_u16; + #[cfg(not(feature = "share_x"))] + #[test] + fn vec_from_share_works() { + let share = Share:: { + y: vec![GF256(2), GF256(3)], + }; + let bytes = Vec::from(&share); + assert_eq!(bytes, vec![2, 3]); + } + + #[cfg(feature = "share_x")] #[test] fn vec_from_share_works() { let share = Share:: { @@ -84,6 +189,15 @@ mod tests { assert_eq!(bytes, vec![1, 2, 3]); } + #[cfg(not(feature = "share_x"))] + #[test] + fn share_from_u8_slice_works() { + let bytes = [1, 2, 3]; + let share = Share::::try_from(&bytes[..]).unwrap(); + assert_eq!(share.y, vec![GF256(1), GF256(2), GF256(3)]); + } + + #[cfg(feature = "share_x")] #[test] fn share_from_u8_slice_works() { let bytes = [1, 2, 3];