diff --git a/.changelog/5897.internal.md b/.changelog/5897.internal.md new file mode 100644 index 00000000000..5601ecc461d --- /dev/null +++ b/.changelog/5897.internal.md @@ -0,0 +1,7 @@ +secret-sharing/src/churp/switch: Verify combined bivariate polynomial + +After all bivariate shares are collected and the switch either +creates a new shareholder or proactivates the share of an existing +one, the new share should be verified to ensure that the verification +matrix of the combined bivariate polynomial satisfies the non-zero +leading term requirements. diff --git a/keymanager/src/churp/handler.rs b/keymanager/src/churp/handler.rs index e5513a97145..1321947445f 100644 --- a/keymanager/src/churp/handler.rs +++ b/keymanager/src/churp/handler.rs @@ -854,7 +854,7 @@ impl Instance { /// be removed, its bivariate polynomial overwritten, and permanently /// lost. /// - /// Note that since the host controls the local storage, he can restart + /// Warning: Since the host controls the local storage, he can restart /// the enclave to create multiple dealers for the same epoch and then /// replace the last backup with a bivariate polynomial from a dealer /// of his choice. Therefore, it is essential to verify the bivariate @@ -866,7 +866,10 @@ impl Instance { dealing_phase: bool, ) -> Result>> { // Create a new dealer. - let dealer = Dealer::create(threshold, dealing_phase, &mut OsRng)?; + let dealer = match dealing_phase { + true => Dealer::new(threshold, &mut OsRng), + false => Dealer::new_proactive(threshold, &mut OsRng), + }?; let dealer = Arc::new(dealer); // Encrypt and store the polynomial in case of a restart. diff --git a/secret-sharing/src/churp/dealer.rs b/secret-sharing/src/churp/dealer.rs index 726df691674..aab3b9130d0 100644 --- a/secret-sharing/src/churp/dealer.rs +++ b/secret-sharing/src/churp/dealer.rs @@ -1,7 +1,7 @@ //! CHURP dealer. use anyhow::Result; -use group::{Group, GroupEncoding}; +use group::{ff::Field, Group, GroupEncoding}; use rand_core::RngCore; use crate::{poly::BivariatePolynomial, vss::VerificationMatrix}; @@ -27,35 +27,65 @@ impl Dealer where G: Group + GroupEncoding, { - /// Creates a new dealer based on the provided parameters. + /// Creates a new dealer of secret bivariate shares, which can be used + /// to recover a randomly selected shared secret. /// - /// A dealer for the dealing phase uses a random bivariate polynomial, - /// otherwise, it uses a zero-holed bivariate polynomial. - pub fn create(threshold: u8, dealing_phase: bool, rng: &mut impl RngCore) -> Result { - match dealing_phase { - true => Self::random(threshold, rng), - false => Self::zero_hole(threshold, rng), - } - } - - /// Creates a new dealer with a predefined shared secret. - pub fn new(threshold: u8, secret: G::Scalar, rng: &mut impl RngCore) -> Result { - let mut bp = Self::random_bivariate_polynomial(threshold, rng)?; - let updated = bp.set_coefficient(0, 0, secret); - debug_assert!(updated); + /// The dealer uses a random bivariate polynomial `B(x, y)` to generate + /// full and reduced bivariate shares, i.e., `B(ID, y)` and `B(x, ID)`, + /// where `ID` represents the identity of a participant, respectively. + /// + /// To ensure that the full and reduced bivariate shares form + /// a (t, n)-sharing and a (2t, n)-sharing of the secret `B(0, 0)`, + /// respectively, the bivariate polynomial is selected such that + /// the polynomials `B(x, y)`, `B(x, 0)`, and `B(0, y)` have non-zero + /// leading terms. This ensures that more than the threshold number + /// of full shares, and more than twice the threshold number of reduced + /// shares, are required to reconstruct the secret. + /// + /// Warning: If multiple dealers are used to generate the shared secret, + /// it is essential to verify that the combined bivariate polynomial + /// also satisfies the aforementioned non-zero leading term requirements. + /// + /// This function is not constant time because it uses rejection sampling. + pub fn new(threshold: u8, rng: &mut impl RngCore) -> Result { + let bp = Self::generate_bivariate_polynomial(threshold, rng)?; Ok(bp.into()) } - /// Creates a new dealer with a random bivariate polynomial. - pub fn random(threshold: u8, rng: &mut impl RngCore) -> Result { - let bp = Self::random_bivariate_polynomial(threshold, rng)?; + /// Creates a new dealer of secret proactive bivariate shares, which can + /// be used to randomize a shared secret. + /// + /// The dealer uses a random zero-hole bivariate polynomial `B(x, y)` + /// to generate full and reduced proactive bivariate shares, i.e., + /// `B(ID, y)` and `B(x, ID)`, where `ID` represents the identity + /// of a participant. Since `B(0, 0) = 0`, adding a proactive share + /// to an existing share does not change the shared secret. + /// + /// Warning: If one or more proactive dealers are used to randomize + /// the shared secret, it is essential to verify that the combined + /// bivariate polynomial still satisfies the non-zero leading term + /// requirements. + /// + /// This function is not constant time because it uses rejection sampling. + pub fn new_proactive(threshold: u8, rng: &mut impl RngCore) -> Result { + let mut bp = Self::generate_bivariate_polynomial(threshold, rng)?; + bp.to_zero_hole(); Ok(bp.into()) } - /// Creates a new dealer with a random zero-hole bivariate polynomial. - pub fn zero_hole(threshold: u8, rng: &mut impl RngCore) -> Result { - let mut bp = Self::random_bivariate_polynomial(threshold, rng)?; - bp.to_zero_hole(); + /// Creates a new dealer of secret bivariate shares, which can be used + /// to recover a predefined shared secret. + /// + /// This function is not constant time because it uses rejection sampling. + #[cfg(test)] + pub fn new_with_secret( + threshold: u8, + secret: G::Scalar, + rng: &mut impl RngCore, + ) -> Result { + let mut bp = Self::generate_bivariate_polynomial(threshold, rng)?; + let updated = bp.set_coefficient(0, 0, secret); + debug_assert!(updated); Ok(bp.into()) } @@ -89,15 +119,41 @@ where SecretShare::new(x, p) } - /// Returns a random bivariate polynomial. - fn random_bivariate_polynomial( + /// Generates a random bivariate polynomial `B(x, y)` such that + /// the polynomials `B(x, y)`, `B(x, 0)`, and `B(0, y)` have non-zero + /// leading term, and the secret `B(0, 0)` is non-zero. + /// + /// This function is not constant time because it uses rejection + /// sampling to ensure that the polynomials have the maximum degree. + /// Additionally, the underlying prime field implementation may also + /// use rejection sampling to generate uniformly random elements. + fn generate_bivariate_polynomial( threshold: u8, rng: &mut impl RngCore, ) -> Result> { let deg_x = threshold; let deg_y = threshold.checked_mul(2).ok_or(Error::ThresholdTooLarge)?; - let bp = BivariatePolynomial::random(deg_x, deg_y, rng); - Ok(bp) + + // When using a random RNG and a large prime field, this loop + // should execute once with an extremely high probability, + // so there is no need to optimize it by randomly selecting + // only the problematic coefficients. + loop { + let bp = BivariatePolynomial::::random(deg_x, deg_y, rng); + + let i = deg_x as usize; + let j = deg_y as usize; + let is_zero_00 = bp.coefficient(0, 0).unwrap().is_zero(); + let is_zero_xy = bp.coefficient(i, j).unwrap().is_zero(); + let is_zero_x0 = bp.coefficient(i, 0).unwrap().is_zero(); + let is_zero_0y = bp.coefficient(0, j).unwrap().is_zero(); + + if (is_zero_00 | is_zero_xy | is_zero_x0 | is_zero_0y).into() { + continue; + } + + return Ok(bp); + } } } @@ -114,7 +170,7 @@ where #[cfg(test)] mod tests { - use rand::{rngs::StdRng, SeedableRng}; + use rand::{rngs::StdRng, Error, RngCore, SeedableRng}; use super::{BivariatePolynomial, HandoffKind}; @@ -123,74 +179,77 @@ mod tests { type Dealer = super::Dealer; #[test] - fn test_create() { + fn test_new() { let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); let test_cases = vec![ - // Zero threshold. - (0, 0, 0, 1, 1), - // Non-zero threshold. - (2, 2, 4, 3, 5), + (0, 0, 0, 1, 1), // Zero threshold. + (2, 2, 4, 3, 5), // Non-zero threshold. ]; for (threshold, deg_x, deg_y, rows, cols) in test_cases { - for dealing_phase in vec![true, false] { - let dealer = Dealer::create(threshold, dealing_phase, &mut rng).unwrap(); - assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x); - assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y); - assert_eq!(dealer.verification_matrix().rows, rows); - assert_eq!(dealer.verification_matrix().cols, cols); - assert_eq!(dealer.verification_matrix().is_zero_hole(), !dealing_phase); - } + let dealer = Dealer::new(threshold, &mut rng).unwrap(); + assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x); + assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y); + assert_eq!(dealer.verification_matrix().rows, rows); + assert_eq!(dealer.verification_matrix().cols, cols); + assert_ne!( + dealer.bivariate_polynomial().coefficient(0, 0), // Zero with negligible probability. + Some(&PrimeField::ZERO) + ); } } #[test] - fn test_new() { + fn test_new_proactive() { let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let tcs = vec![ - // Zero threshold. - (0, 0, 0, 0, 1, 1), - // Non-zero threshold. - (100, 2, 2, 4, 3, 5), + let test_cases = vec![ + (0, 0, 0, 1, 1), // Zero threshold. + (2, 2, 4, 3, 5), // Non-zero threshold. ]; - for (secret, threshold, deg_x, deg_y, rows, cols) in tcs { - let secret = PrimeField::from_u64(secret); - let dealer = Dealer::new(threshold, secret, &mut rng).unwrap(); + for (threshold, deg_x, deg_y, rows, cols) in test_cases { + let dealer = Dealer::new_proactive(threshold, &mut rng).unwrap(); assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x); assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y); assert_eq!(dealer.verification_matrix().rows, rows); assert_eq!(dealer.verification_matrix().cols, cols); assert_eq!( dealer.bivariate_polynomial().coefficient(0, 0), - Some(&secret) + Some(&PrimeField::ZERO) ); } } #[test] - fn test_random() { - let threshold = 2; + fn test_new_with_secret() { let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let dealer = Dealer::random(threshold, &mut rng).unwrap(); - assert!(!dealer.verification_matrix().is_zero_hole()); // Zero-hole with negligible probability. - } - #[test] - fn test_zero_hole() { - let threshold = 2; - let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let dealer = Dealer::zero_hole(threshold, &mut rng).unwrap(); - assert!(dealer.verification_matrix().is_zero_hole()); + let test_cases = vec![ + (0, 0, 0, 1, 1, 0), // Zero threshold. + (2, 2, 4, 3, 5, 100), // Non-zero threshold. + ]; + + for (threshold, deg_x, deg_y, rows, cols, secret) in test_cases { + let secret = PrimeField::from_u64(secret); + let dealer = Dealer::new_with_secret(threshold, secret, &mut rng).unwrap(); + assert_eq!(dealer.bivariate_polynomial().deg_x, deg_x); + assert_eq!(dealer.bivariate_polynomial().deg_y, deg_y); + assert_eq!(dealer.verification_matrix().rows, rows); + assert_eq!(dealer.verification_matrix().cols, cols); + assert_eq!( + dealer.bivariate_polynomial().coefficient(0, 0), + Some(&secret) + ); + } } #[test] fn test_make_share() { let threshold = 2; let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let dealer = Dealer::random(threshold, &mut rng).unwrap(); + let dealer = Dealer::new(threshold, &mut rng).unwrap(); let x = PrimeField::from_u64(2); let test_cases = vec![ @@ -205,6 +264,75 @@ mod tests { } } + #[test] + fn test_generate_bivariate_polynomial() { + /// A custom RNG that fills the first few slices with zeros, + /// and subsequent slices with ones. + struct ZeroOneRng { + /// Tracks how many times the RNG has been called. + counter: usize, + /// The number of slices that should be filled with zeros. + limit: usize, + } + + impl ZeroOneRng { + /// Creates a new generator with the given limit. + fn new(limit: usize) -> Self { + Self { limit, counter: 0 } + } + + // Returns the total number of times the generator has been invoked. + fn total(&self) -> usize { + self.counter + } + } + + impl RngCore for ZeroOneRng { + fn next_u32(&mut self) -> u32 { + panic!("not implemented") + } + + fn next_u64(&mut self) -> u64 { + panic!("not implemented") + } + + fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Error> { + panic!("not implemented") + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + match self.counter < self.limit { + true => dest.fill(0), + false => dest.fill(1), + } + self.counter += 1; + } + } + + let test_cases = vec![0, 2, 4]; + + for threshold in test_cases { + // Prepare RNG that will generate the first two bivariate polynomials + // with all coefficients set to zero. + let num_terms = (threshold + 1) * (2 * threshold + 1); + let mut rng = ZeroOneRng::new(2 * num_terms); + + // Generate a random bivariate polynomial and verify leading coefficients. + let bp = Dealer::generate_bivariate_polynomial(threshold as u8, &mut rng).unwrap(); + let f = bp.eval_y(&PrimeField::ZERO); + let g = bp.eval_x(&PrimeField::ZERO); + let i = threshold; + let j = 2 * threshold; + + assert!(!Into::::into(bp.coefficient(i, j).unwrap().is_zero())); + assert!(!Into::::into(f.coefficient(i).unwrap().is_zero())); + assert!(!Into::::into(g.coefficient(j).unwrap().is_zero())); + + // Verify that the RNG generated coefficients for three polynomials. + assert_eq!(3 * num_terms, rng.total()); + } + } + #[test] fn test_from() { let bp = BivariatePolynomial::zero(2, 3); diff --git a/secret-sharing/src/churp/errors.rs b/secret-sharing/src/churp/errors.rs index 96857c61c5e..6e4d4a9aa07 100644 --- a/secret-sharing/src/churp/errors.rs +++ b/secret-sharing/src/churp/errors.rs @@ -6,6 +6,8 @@ pub enum Error { InvalidKind, #[error("invalid polynomial")] InvalidPolynomial, + #[error("insecure bivariate polynomial")] + InsecureBivariatePolynomial, #[error("invalid switch point")] InvalidSwitchPoint, #[error("invalid state")] @@ -22,6 +24,8 @@ pub enum Error { PolynomialDegreeMismatch, #[error("shareholder encoding failed")] ShareholderEncodingFailed, + #[error("shareholder proactivization already completed")] + ShareholderProactivizationCompleted, #[error("shareholder identity mismatch")] ShareholderIdentityMismatch, #[error("shareholder identity required")] diff --git a/secret-sharing/src/churp/handoff.rs b/secret-sharing/src/churp/handoff.rs index 8ba1e40b594..c919dfe1298 100644 --- a/secret-sharing/src/churp/handoff.rs +++ b/secret-sharing/src/churp/handoff.rs @@ -422,9 +422,19 @@ mod tests { n: usize, rng: &mut impl RngCore, ) -> Vec { - (0..n) - .map(|_| Dealer::create(threshold, dealing_phase, rng).unwrap()) - .collect() + let mut dealers = Vec::with_capacity(n); + + for _ in 0..n { + let dealer = match dealing_phase { + true => Dealer::new(threshold, rng), + false => Dealer::new_proactive(threshold, rng), + } + .unwrap(); + + dealers.push(dealer); + } + + dealers } #[test] diff --git a/secret-sharing/src/churp/player.rs b/secret-sharing/src/churp/player.rs index e1be2592b31..8dda962da6b 100644 --- a/secret-sharing/src/churp/player.rs +++ b/secret-sharing/src/churp/player.rs @@ -98,7 +98,7 @@ mod tests { // Prepare scheme. let threshold = 2; let secret = PrimeField::from_u64(100); - let dealer = Dealer::new(threshold, secret, &mut OsRng).unwrap(); + let dealer = Dealer::new_with_secret(threshold, secret, &mut OsRng).unwrap(); let player = Player::new(threshold, kind); let min_shares = player.min_shares() as u64; @@ -152,7 +152,7 @@ mod tests { let secret = PrimeField::from_u64(100); let hash = Suite::hash_to_group(key_id, dst).unwrap(); let key = hash * secret; - let dealer = Dealer::new(threshold, secret, &mut OsRng).unwrap(); + let dealer = Dealer::new_with_secret(threshold, secret, &mut OsRng).unwrap(); let player = Player::new(threshold, kind); let min_shares = player.min_shares() as u64; diff --git a/secret-sharing/src/churp/shareholder.rs b/secret-sharing/src/churp/shareholder.rs index 00958c96f8f..7dd1d685136 100644 --- a/secret-sharing/src/churp/shareholder.rs +++ b/secret-sharing/src/churp/shareholder.rs @@ -190,6 +190,30 @@ where return Err(Error::VerificationMatrixZeroHoleMismatch.into()); } + // Verify that the bivariate polynomial `B(x, y)`, from which + // the verification matrix was constructed and the share was derived, + // satisfies the non-zero leading term requirements. Specifically, + // the polynomials `B(x, y)`, `B(x, 0)`, and `B(0, y)` must have + // non-zero leading terms. + if threshold > 0 { + // Skipping the special case where the bivariate polynomial has only + // one coefficient. The verification of this coefficient has already + // been done above, when we checked if the verification matrix + // is zero-hole. + let i = rows - 1; + let j = cols - 1; + + if self.vm.element(i, j).unwrap().is_identity().into() { + return Err(Error::InsecureBivariatePolynomial.into()); + } + if self.vm.element(i, 0).unwrap().is_identity().into() { + return Err(Error::InsecureBivariatePolynomial.into()); + } + if self.vm.element(0, j).unwrap().is_identity().into() { + return Err(Error::InsecureBivariatePolynomial.into()); + } + } + Ok(()) } diff --git a/secret-sharing/src/churp/switch.rs b/secret-sharing/src/churp/switch.rs index bd4c8e90c93..52691631cf4 100644 --- a/secret-sharing/src/churp/switch.rs +++ b/secret-sharing/src/churp/switch.rs @@ -552,10 +552,14 @@ where return Err(Error::NotEnoughBivariateShares.into()); } - // The values cannot be empty since the constructor verifies that - // the number of shareholders is greater than zero. - let p = self.p.take().unwrap(); - let vm = self.vm.take().unwrap(); + let p = self + .p + .take() + .ok_or(Error::ShareholderProactivizationCompleted)?; + let vm = self + .vm + .take() + .ok_or(Error::ShareholderProactivizationCompleted)?; let shareholder = match &self.shareholder { Some(shareholder) => shareholder.proactivize(&p, &vm)?, @@ -565,6 +569,12 @@ where } }; + // Ensure that the combined bivariate polynomial satisfies + // the non-zero leading term requirements. + shareholder + .verifiable_share() + .verify(self.threshold, false, self.full_share)?; + Ok(shareholder) } } @@ -733,7 +743,6 @@ mod tests { #[test] fn test_bivariate_shares() { let threshold = 2; - let zero_hole = true; let me = prepare_shareholder(1); let shareholders = prepare_shareholders(&[1, 2, 3]); @@ -750,78 +759,84 @@ mod tests { // Happy path. for full_share in vec![false, true] { - let mut bs = BivariateShares::::new( - threshold, - zero_hole, - full_share, - me, - shareholders.clone(), - None, - ) - .unwrap(); + for zero_hole in vec![false, true] { + let mut bs = BivariateShares::::new( + threshold, + zero_hole, + full_share, + me, + shareholders.clone(), + None, + ) + .unwrap(); + + let me = 1; + let mut sh = 2; + + // Add invalid share (invalid threshold). + let res = + add_bivariate_shares(threshold + 1, zero_hole, full_share, me, me, &mut bs); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().to_string(), + Error::VerificationMatrixDimensionMismatch.to_string() + ); - let me = 1; - let mut sh = 2; + // Add share. + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, me, &mut bs); + assert!(res.is_ok()); + assert!(!res.unwrap()); - // Add invalid share (invalid threshold). - let res = add_bivariate_shares(threshold + 1, zero_hole, full_share, me, me, &mut bs); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - Error::VerificationMatrixDimensionMismatch.to_string() - ); + // Add another share twice. + assert!(bs.needs_bivariate_share(&prepare_shareholder(sh))); - // Add share. - let res = add_bivariate_shares(threshold, zero_hole, full_share, me, me, &mut bs); - assert!(res.is_ok()); - assert!(!res.unwrap()); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); + assert!(res.is_ok()); + assert!(!res.unwrap()); - // Add another share twice. - assert!(bs.needs_bivariate_share(&prepare_shareholder(sh))); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().to_string(), + Error::DuplicateShareholder.to_string() + ); - let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); - assert!(res.is_ok()); - assert!(!res.unwrap()); + assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); + sh += 1; + + // Try to collect the polynomial and verification matrix. + let res = bs.proactivize_shareholder(); + assert!(res.is_err()); + unsafe { + assert_eq!( + res.unwrap_err_unchecked().to_string(), + Error::NotEnoughBivariateShares.to_string() + ); + } - let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - Error::DuplicateShareholder.to_string() - ); + // Add the last share. + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); + assert!(res.is_ok()); + assert!(res.unwrap()); // Enough shares. + sh += 1; - assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); - sh += 1; + // Unknown shareholder. + assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); - // Try to collect the polynomial and verification matrix. - let res = bs.proactivize_shareholder(); - assert!(res.is_err()); - unsafe { + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); + assert!(res.is_err()); assert_eq!( - res.unwrap_err_unchecked().to_string(), - Error::NotEnoughBivariateShares.to_string() + res.unwrap_err().to_string(), + Error::UnknownShareholder.to_string() ); - } - - // Add the last share. - let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); - assert!(res.is_ok()); - assert!(res.unwrap()); // Enough shares. - sh += 1; - // Unknown shareholder. - assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); - - let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); - assert!(res.is_err()); - assert_eq!( - res.unwrap_err().to_string(), - Error::UnknownShareholder.to_string() - ); - - // Try to collect the polynomial and verification matrix again. - let res = bs.proactivize_shareholder(); - assert!(res.is_ok()); + // Try to collect the polynomial and verification matrix again. + let res = bs.proactivize_shareholder(); + match zero_hole { + true => assert!(res.is_err()), // The combined polynomial has zero secret (not allowed). + false => assert!(res.is_ok()), // The combined polynomial has non-zero secret. + } + } } } } diff --git a/secret-sharing/src/vss/matrix.rs b/secret-sharing/src/vss/matrix.rs index 2e9890ba1e9..8f153e3c1ff 100644 --- a/secret-sharing/src/vss/matrix.rs +++ b/secret-sharing/src/vss/matrix.rs @@ -50,6 +50,11 @@ where (self.rows, self.cols) } + /// Returns the element `m_{i,j}` of the verification matrix. + pub fn element(&self, i: usize, j: usize) -> Option<&G> { + self.m.get(i).and_then(|bi| bi.get(j)) + } + /// Returns true if and only if `M_{0,0}` is the identity element /// of the group. pub fn is_zero_hole(&self) -> bool { @@ -349,51 +354,75 @@ where #[cfg(test)] mod tests { - use group::Group; + use group::Group as _; use rand::{rngs::StdRng, SeedableRng}; - use crate::vss::VerificationMatrix; + use crate::{poly, vss}; - use super::BivariatePolynomial; + type PrimeField = p384::Scalar; + type Group = p384::ProjectivePoint; + type BivariatePolynomial = poly::BivariatePolynomial; + type VerificationMatrix = vss::VerificationMatrix; - fn scalar(value: i64) -> p384::Scalar { + fn scalar(value: i64) -> PrimeField { scalars(&vec![value])[0] } - fn scalars(values: &[i64]) -> Vec { + fn scalars(values: &[i64]) -> Vec { values .iter() .map(|&w| match w.is_negative() { - false => p384::Scalar::from_u64(w as u64), - true => p384::Scalar::from_u64(-w as u64).neg(), + false => PrimeField::from_u64(w as u64), + true => PrimeField::from_u64(-w as u64).neg(), }) .collect() } #[test] - fn test_new() { + fn test_from() { // Two non-zero coefficients (fast). let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let mut bp = BivariatePolynomial::::zero(2, 3); - assert!(bp.set_coefficient(0, 0, p384::Scalar::ONE)); - assert!(bp.set_coefficient(2, 2, p384::Scalar::ONE.double())); + let mut bp = BivariatePolynomial::zero(2, 3); + assert!(bp.set_coefficient(0, 0, PrimeField::ONE)); + assert!(bp.set_coefficient(1, 2, PrimeField::ONE.double())); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert_eq!(vm.m.len(), 3); for (i, mi) in vm.m.iter().enumerate() { assert_eq!(mi.len(), 4); for (j, mij) in mi.iter().enumerate() { match (i, j) { - (0, 0) => assert_eq!(mij, &p384::ProjectivePoint::GENERATOR), - (2, 2) => assert_eq!(mij, &p384::ProjectivePoint::GENERATOR.double()), - _ => assert_eq!(mij, &p384::ProjectivePoint::IDENTITY), + (0, 0) => assert_eq!(mij, &Group::GENERATOR), + (1, 2) => assert_eq!(mij, &Group::GENERATOR.double()), + _ => assert_eq!(mij, &Group::IDENTITY), } } } // Random bivariate polynomial (slow). - let bp: BivariatePolynomial = BivariatePolynomial::random(5, 10, &mut rng); - let _ = VerificationMatrix::::from(&bp); + let bp = BivariatePolynomial::random(5, 10, &mut rng); + let _ = VerificationMatrix::from(&bp); + } + + #[test] + fn test_dimensions() { + let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); + let bp = BivariatePolynomial::random(5, 10, &mut rng); + + let vm = VerificationMatrix::from(&bp); + assert_eq!((6, 11), vm.dimensions()); + } + + #[test] + fn test_element() { + let c = scalar(42); + let e = Group::GENERATOR * c; + + let mut bp = BivariatePolynomial::zero(2, 3); + assert!(bp.set_coefficient(1, 2, c)); + + let vm = VerificationMatrix::from(&bp); + assert_eq!(&e, vm.element(1, 2).unwrap()); } #[test] @@ -402,9 +431,9 @@ mod tests { let x2 = scalar(2); let x3 = scalar(3); - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 3, &mut rng); + let bp = BivariatePolynomial::random(2, 3, &mut rng); let s = bp.eval_x(&x2).eval(&x3); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert!(vm.verify(&x2, &x3, &s)); assert!(!vm.verify(&x3, &x2, &s)); } @@ -415,8 +444,8 @@ mod tests { let x2 = scalar(2); let x3 = scalar(3); - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 3, &mut rng); - let vm = VerificationMatrix::::from(&bp); + let bp = BivariatePolynomial::random(2, 3, &mut rng); + let vm = VerificationMatrix::from(&bp); let p = bp.eval_y(&x2); let vv = vm.verification_vector_for_x(&x2); @@ -432,8 +461,8 @@ mod tests { let x2 = scalar(2); let x3 = scalar(3); - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 3, &mut rng); - let vm = VerificationMatrix::::from(&bp); + let bp = BivariatePolynomial::random(2, 3, &mut rng); + let vm = VerificationMatrix::from(&bp); let p = bp.eval_x(&x2); let vv = vm.verification_vector_for_y(&x2); @@ -449,16 +478,16 @@ mod tests { let x2 = scalar(2); // Asymmetric bivariate polynomial. - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 3, &mut rng); + let bp = BivariatePolynomial::random(2, 3, &mut rng); let p = bp.eval_x(&x2); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert!(vm.verify_x(&x2, &p)); assert!(!vm.verify_y(&x2, &p)); // Invalid degree. // Symmetric bivariate polynomial. - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 2, &mut rng); + let bp = BivariatePolynomial::random(2, 2, &mut rng); let p = bp.eval_x(&x2); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert!(vm.verify_x(&x2, &p)); assert!(!vm.verify_y(&x2, &p)); // Valid degree, but verification failed. } @@ -469,16 +498,16 @@ mod tests { let y2 = scalar(2); // Asymmetric bivariate polynomial. - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 3, &mut rng); + let bp = BivariatePolynomial::random(2, 3, &mut rng); let p = bp.eval_y(&y2); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert!(!vm.verify_x(&y2, &p)); // Invalid degree. assert!(vm.verify_y(&y2, &p)); // Symmetric bivariate polynomial. - let bp: BivariatePolynomial = BivariatePolynomial::random(2, 2, &mut rng); + let bp = BivariatePolynomial::random(2, 2, &mut rng); let p = bp.eval_y(&y2); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); assert!(!vm.verify_x(&y2, &p)); // Valid degree, but verification failed. assert!(vm.verify_y(&y2, &p)); } @@ -486,23 +515,23 @@ mod tests { #[test] fn test_serialization() { let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); - let bp = BivariatePolynomial::::random(2, 3, &mut rng); - let vm = VerificationMatrix::::from(&bp); - let restored = VerificationMatrix::::from_bytes(&vm.to_bytes()) - .expect("deserialization should succeed"); + let bp = BivariatePolynomial::random(2, 3, &mut rng); + let vm = VerificationMatrix::from(&bp); + let restored = + VerificationMatrix::from_bytes(&vm.to_bytes()).expect("deserialization should succeed"); assert_eq!(vm, restored); } #[test] fn test_element_byte_size() { - let size = VerificationMatrix::::element_byte_size(); + let size = VerificationMatrix::element_byte_size(); assert_eq!(size, 49); } #[test] fn test_byte_size() { - let size = VerificationMatrix::::byte_size(2, 3); + let size = VerificationMatrix::byte_size(2, 3); assert_eq!(size, 2 + 2 * 3 * 49); } @@ -512,8 +541,8 @@ mod tests { let c2 = vec![scalars(&[1, 2]), scalars(&[3, 4]), scalars(&[5, 6])]; let bp1 = BivariatePolynomial::with_coefficients(c1); let bp2 = BivariatePolynomial::with_coefficients(c2); - let vm1 = VerificationMatrix::::from(&bp1); - let vm2 = VerificationMatrix::::from(&bp2); + let vm1 = VerificationMatrix::from(&bp1); + let vm2 = VerificationMatrix::from(&bp2); let c = vec![ scalars(&[1 + 1, 2 + 2, 3, 4]), @@ -521,7 +550,7 @@ mod tests { scalars(&[5, 6, 0, 0]), ]; let bp = BivariatePolynomial::with_coefficients(c); - let vm = VerificationMatrix::::from(&bp); + let vm = VerificationMatrix::from(&bp); let sum = &vm1 + &vm2; assert_eq!(sum.rows, 3);