diff --git a/Cargo.lock b/Cargo.lock index fc48089f7..77e6375c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4716,22 +4716,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ndarray" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", - "serde", -] - [[package]] name = "nix" version = "0.26.4" @@ -5469,21 +5453,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -7623,7 +7592,6 @@ dependencies = [ "error-utils", "g2p", "itertools 0.14.0", - "ndarray", "num-traits", "paste", "proptest", @@ -7687,7 +7655,6 @@ dependencies = [ "futures-util", "itertools 0.14.0", "mockall", - "ndarray", "num-integer", "num-traits", "paste", diff --git a/Cargo.toml b/Cargo.toml index 9b6e35b93..636f6015b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,7 +153,6 @@ k256 = "=0.13.4" # secp256k1 elliptic curve - LOW RISK: RustCrypto org, 33M+ do minijinja = { version = "=2.11.0", features = ["loader"] } # Template engine - HIGH RISK: Individual maintainer (mitsuhiko), despite exceptional track record ml-kem = { version = "=0.2.2", features = ["zeroize"] } # ML-KEM (Kyber) post-quantum KEM - MEDIUM RISK: New standard implementation, needs security audit mockall = "=0.13.1" # Mocking for tests - HIGH RISK: Individual maintainer (asomers), test-only dependency -ndarray = { version = "=0.16.1", features = ["serde"] } # N-dimensional arrays - LOW RISK: rust-ndarray team nom = "=8.0.0" # Parser combinator library - HIGH RISK: Individual maintainer (Geal), despite 319M+ downloads num-integer = "=0.1.46" # Integer utilities - LOW RISK: rust-num team num-traits = "=0.2.19" # Numeric traits - LOW RISK: rust-num team diff --git a/core/threshold-algebra/Cargo.toml b/core/threshold-algebra/Cargo.toml index 7cc55522b..e289786d0 100644 --- a/core/threshold-algebra/Cargo.toml +++ b/core/threshold-algebra/Cargo.toml @@ -11,7 +11,6 @@ threshold-types.workspace = true g2p.workspace = true error-utils.workspace = true itertools.workspace = true -ndarray.workspace = true rand.workspace = true serde.workspace = true sha3.workspace = true @@ -33,3 +32,7 @@ rstest.workspace = true [[bench]] name = "bivariate" harness = false + +[[bench]] +name = "matrix" +harness = false diff --git a/core/threshold-algebra/benches/matrix.rs b/core/threshold-algebra/benches/matrix.rs new file mode 100644 index 000000000..38795d3f8 --- /dev/null +++ b/core/threshold-algebra/benches/matrix.rs @@ -0,0 +1,73 @@ +use aes_prng::AesRng; +use criterion::{Criterion, criterion_group, criterion_main}; +use rand::SeedableRng; +use std::hint::black_box; +use threshold_algebra::{ + galois_rings::degree_4::ResiduePolyF4Z128, matrix::VdmMatrix, structure_traits::Sample, +}; + +const PRODUCTION_PARTIES: usize = 13; +const PRODUCTION_THRESHOLD: usize = 4; +const EXTRACTED_WIDTH: usize = PRODUCTION_PARTIES - PRODUCTION_THRESHOLD; + +fn sample_vec(rng: &mut AesRng, len: usize) -> Vec { + (0..len).map(|_| ResiduePolyF4Z128::sample(rng)).collect() +} + +fn bench_single_sharing_vdm(c: &mut Criterion) { + let mut rng = AesRng::seed_from_u64(0); + let shares = sample_vec(&mut rng, PRODUCTION_PARTIES); + let vdm = VdmMatrix::::from_exceptional_sequence( + PRODUCTION_PARTIES, + EXTRACTED_WIDTH, + ) + .unwrap(); + + c.bench_function("matrix/production/single_sharing_vdm/n13_t4", |b| { + b.iter(|| black_box(black_box(&vdm).mul_vector(black_box(&shares)).unwrap())) + }); +} + +fn bench_double_sharing_vdm(c: &mut Criterion) { + let mut rng = AesRng::seed_from_u64(1); + let shares_t = sample_vec(&mut rng, PRODUCTION_PARTIES); + let shares_2t = sample_vec(&mut rng, PRODUCTION_PARTIES); + let vdm = VdmMatrix::::from_exceptional_sequence( + PRODUCTION_PARTIES, + EXTRACTED_WIDTH, + ) + .unwrap(); + + c.bench_function("matrix/production/double_sharing_vdm/n13_t4", |b| { + b.iter(|| { + black_box(( + black_box(&vdm).mul_vector(black_box(&shares_t)).unwrap(), + black_box(&vdm).mul_vector(black_box(&shares_2t)).unwrap(), + )) + }) + }); +} + +fn bench_robust_prss_vdm(c: &mut Criterion) { + let mut rng = AesRng::seed_from_u64(2); + let shares = sample_vec(&mut rng, PRODUCTION_PARTIES); + let vdm = VdmMatrix::::from_exceptional_sequence( + PRODUCTION_PARTIES, + EXTRACTED_WIDTH, + ) + .unwrap(); + + // Keep the Criterion ID stable so this branch can compare against the + // original transposed ndarray baseline. + c.bench_function("matrix/production/robust_prss_transposed_vdm/n13_t4", |b| { + b.iter(|| black_box(black_box(&vdm).mul_vector(black_box(&shares)).unwrap())) + }); +} + +criterion_group!( + matrix, + bench_single_sharing_vdm, + bench_double_sharing_vdm, + bench_robust_prss_vdm, +); +criterion_main!(matrix); diff --git a/core/threshold-algebra/src/matrix.rs b/core/threshold-algebra/src/matrix.rs index bef220e23..842f4d1f7 100644 --- a/core/threshold-algebra/src/matrix.rs +++ b/core/threshold-algebra/src/matrix.rs @@ -1,18 +1,94 @@ -use crate::structure_traits::{One, Ring}; +use crate::structure_traits::{One, Ring, RingWithExceptionalSequence}; use anyhow::Result; use error_utils::anyhow_error_and_log; -use itertools::Itertools; -use ndarray::{Array, ArrayD, IxDyn}; use std::ops::Mul; -pub trait MatrixMul: Sized { - type Output; - fn matmul(&self, rhs: &Rhs) -> Result; +/// Row-major Vandermonde matrix used for randomness extraction. +#[derive(Debug, Default)] +pub struct VdmMatrix { + height: usize, + width: usize, + coefs: Vec, +} + +impl VdmMatrix { + /// Returns true if the matrix has no coefficients. + pub fn is_empty(&self) -> bool { + self.coefs.is_empty() + } + + /// Returns the matrix height. + pub fn height(&self) -> usize { + self.height + } + + /// Returns the matrix width. + pub fn width(&self) -> usize { + self.width + } +} + +impl VdmMatrix { + /// Creates the VDM matrix where row `i` is `[1, alpha_i, ..., alpha_i^(width - 1)]`. + /// + /// `alpha_i` is the `i + 1` entry from the exceptional sequence. + pub fn from_exceptional_sequence(height: usize, width: usize) -> Result { + if width == 0 { + return Ok(Self { + height, + width, + coefs: Vec::new(), + }); + } + + let mut coefs = Vec::with_capacity(height * width); + for idx in 0..height { + let point = Z::get_from_exceptional_sequence(idx + 1)?; + let mut power = Z::ONE; + coefs.push(power); + for _ in 1..width { + power *= point; + coefs.push(power); + } + } + + debug_assert_eq!(coefs.len(), height * width); + Ok(Self { + height, + width, + coefs, + }) + } +} + +impl VdmMatrix { + /// Multiplies a row vector by this VDM matrix. + pub fn mul_vector(&self, lhs: &[Z]) -> Result> { + if lhs.len() != self.height { + return Err(anyhow_error_and_log(format!( + "Cannot multiply vector of length {} by VDM matrix of shape ({}, {})", + lhs.len(), + self.height, + self.width, + ))); + } + if self.width == 0 { + return Ok(Vec::new()); + } + + let mut res = vec![Z::ZERO; self.width]; + for (lhs_coef, row) in lhs.iter().zip(self.coefs.chunks_exact(self.width)) { + for (res_coef, matrix_coef) in res.iter_mut().zip(row) { + *res_coef += *lhs_coef * *matrix_coef; + } + } + Ok(res) + } } /// Computes powers of a specific point up to degree: p^0, p^1,...,p^degree -pub fn compute_powers + Copy>(point: Z, degree: usize) -> Vec { +pub(crate) fn compute_powers + Copy>(point: Z, degree: usize) -> Vec { let mut powers_of_point = Vec::with_capacity(degree + 1); powers_of_point.push(Z::ONE); for i in 1..=degree { @@ -33,91 +109,6 @@ pub fn compute_powers_list + Copy>( alpha_powers } -impl MatrixMul> for ArrayD { - type Output = ArrayD; - - fn matmul(&self, rhs: &ArrayD) -> Result { - match (self.ndim(), rhs.ndim()) { - (1, 1) => { - if self.dim() != rhs.dim() { - return Err(anyhow_error_and_log(format!( - "Cannot compute multiplication between rank 1 tensor where dimension of lhs {:?} and rhs {:?}", - self.dim(), - rhs.dim() - ))); - } - if self.len() != rhs.len() { - return Err(anyhow_error_and_log(format!( - "Cannot multiply lhs of {:?} elements and rhs of {:?} elements for rank 1 tensors", - self.len(), - rhs.len() - ))); - } - let res = self - .iter() - .zip_eq(rhs) - .fold(Z::ZERO, |acc, (a, b)| acc + *a * *b); - Ok(Array::from_elem(IxDyn(&[1]), res).into_dyn()) - } - (1, 2) => { - if self.dim()[0] != rhs.dim()[0] { - Err(anyhow_error_and_log(format!( - "Cannot compute multiplication between rank 1 tensor and rank 2 tensor where dimension of lhs {:?} and rhs {:?}", - self.dim(), - rhs.dim() - ))) - } else { - let mut res = Vec::with_capacity(rhs.shape()[1]); - for col in rhs.columns() { - if col.len() != self.len() { - return Err(anyhow_error_and_log(format!( - "Cannot multiply lhs of {:?} elements and rhs of {:?} elements for rank 1 tensors and rank 2 tensors", - self.len(), - rhs.len() - ))); - } - let s = col - .iter() - .zip_eq(self) - .fold(Z::ZERO, |acc, (a, b)| acc + *b * *a); - res.push(s); - } - Ok(Array::from_vec(res).into_dyn()) - } - } - (2, 1) => { - if self.dim()[1] != rhs.dim()[0] { - Err(anyhow_error_and_log(format!( - "Cannot compute multiplication between rank 2 tensor and rank 1 tensor where dimension of lhs {:?} and rhs {:?}", - self.dim(), - rhs.dim() - ))) - } else { - let mut res = Vec::with_capacity(self.shape()[0]); - for row in self.rows() { - if row.len() != rhs.len() { - return Err(anyhow_error_and_log(format!( - "Cannot multiply lhs of {:?} elements and rhs of {:?} elements for rank 2 tensors and rank 1 tensors", - self.len(), - rhs.len() - ))); - } - let s = row - .iter() - .zip_eq(rhs) - .fold(Z::ZERO, |acc, (a, b)| acc + *b * *a); - res.push(s); - } - Ok(Array::from_vec(res).into_dyn()) - } - } - (l_rank, r_rank) => Err(anyhow_error_and_log(format!( - "Matmul not implemented for tensors of rank {l_rank:?}, {r_rank:?}", - ))), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -153,53 +144,51 @@ mod tests { } #[test] - fn matmul_rank1_dot() { - let a = ArrayD::from_shape_vec(IxDyn(&[3]), vec![rp(1), rp(2), rp(3)]).unwrap(); - let b = ArrayD::from_shape_vec(IxDyn(&[3]), vec![rp(4), rp(5), rp(6)]).unwrap(); - let res = a.matmul(&b).unwrap(); - // 1*4 + 2*5 + 3*6 = 32 - assert_eq!(res.into_raw_vec_and_offset().0, vec![rp(32)]); + fn vdm_matrix_mul_left_vector() { + let vdm = VdmMatrix { + height: 2, + width: 3, + coefs: vec![rp(1), rp(2), rp(3), rp(4), rp(5), rp(6)], + }; + let res = vdm.mul_vector(&[rp(1), rp(2)]).unwrap(); + assert_eq!(res, vec![rp(9), rp(12), rp(15)]); } #[test] - fn matmul_vector_matrix() { - // [1,2] * [[1,2,3],[4,5,6]] = [9, 12, 15] - let v = ArrayD::from_shape_vec(IxDyn(&[2]), vec![rp(1), rp(2)]).unwrap(); - let m = ArrayD::from_shape_vec( - IxDyn(&[2, 3]), - vec![rp(1), rp(2), rp(3), rp(4), rp(5), rp(6)], - ) - .unwrap(); - let res = v.matmul(&m).unwrap(); - assert_eq!(res.into_raw_vec_and_offset().0, vec![rp(9), rp(12), rp(15)]); + fn vdm_matrix_dim_mismatch_err() { + let vdm = VdmMatrix { + height: 2, + width: 2, + coefs: vec![rp(1); 4], + }; + assert!(vdm.mul_vector(&[rp(1); 3]).is_err()); } #[test] - fn matmul_matrix_vector() { - // [[1,2,3],[4,5,6]] * [7,8,9] = [50, 122] - let m = ArrayD::from_shape_vec( - IxDyn(&[2, 3]), - vec![rp(1), rp(2), rp(3), rp(4), rp(5), rp(6)], - ) - .unwrap(); - let v = ArrayD::from_shape_vec(IxDyn(&[3]), vec![rp(7), rp(8), rp(9)]).unwrap(); - let res = m.matmul(&v).unwrap(); - assert_eq!(res.into_raw_vec_and_offset().0, vec![rp(50), rp(122)]); + fn vdm_matrix_from_exceptional_sequence_zero_width() { + let vdm = VdmMatrix::::from_exceptional_sequence(2, 0).unwrap(); + + assert!(vdm.is_empty()); + assert_eq!(vdm.height(), 2); + assert_eq!(vdm.width(), 0); + assert_eq!(vdm.mul_vector(&[rp(1), rp(2)]).unwrap(), Vec::new()); } #[test] - fn matmul_dim_mismatches_err() { - let v3 = ArrayD::from_shape_vec(IxDyn(&[3]), vec![rp(1); 3]).unwrap(); - let v2 = ArrayD::from_shape_vec(IxDyn(&[2]), vec![rp(1); 2]).unwrap(); - // rank-1 length mismatch - assert!(v3.matmul(&v2).is_err()); - // rank mismatch on rank-2 sides - let m22 = ArrayD::from_shape_vec(IxDyn(&[2, 2]), vec![rp(1); 4]).unwrap(); - assert!(m22.matmul(&m22).is_err()); - // (1, 2) shape mismatch - let m23 = ArrayD::from_shape_vec(IxDyn(&[2, 3]), vec![rp(1); 6]).unwrap(); - assert!(v3.matmul(&m23).is_err()); - // (2, 1) shape mismatch - assert!(m23.matmul(&v2).is_err()); + fn vdm_matrix_from_exceptional_sequence() { + let vdm = VdmMatrix::::from_exceptional_sequence(2, 3).unwrap(); + let alpha_1 = ResiduePolyF4Z128::get_from_exceptional_sequence(1).unwrap(); + let alpha_2 = ResiduePolyF4Z128::get_from_exceptional_sequence(2).unwrap(); + + assert_eq!( + vdm.mul_vector(&[ResiduePolyF4Z128::ONE, ResiduePolyF4Z128::ZERO]) + .unwrap(), + vec![ResiduePolyF4Z128::ONE, alpha_1, alpha_1 * alpha_1] + ); + assert_eq!( + vdm.mul_vector(&[ResiduePolyF4Z128::ZERO, ResiduePolyF4Z128::ONE]) + .unwrap(), + vec![ResiduePolyF4Z128::ONE, alpha_2, alpha_2 * alpha_2,] + ); } } diff --git a/core/threshold-execution/Cargo.toml b/core/threshold-execution/Cargo.toml index 82dd19d59..9b6bbad8c 100644 --- a/core/threshold-execution/Cargo.toml +++ b/core/threshold-execution/Cargo.toml @@ -24,7 +24,6 @@ futures = { workspace = true, optional = true } hashing.workspace = true itertools.workspace = true mockall.workspace = true -ndarray.workspace = true num-integer.workspace = true num-traits.workspace = true rand.workspace = true diff --git a/core/threshold-execution/src/large_execution/double_sharing.rs b/core/threshold-execution/src/large_execution/double_sharing.rs index fe3d6012a..5eff5116b 100644 --- a/core/threshold-execution/src/large_execution/double_sharing.rs +++ b/core/threshold-execution/src/large_execution/double_sharing.rs @@ -1,16 +1,12 @@ -use super::{ - local_double_share::{DoubleShares, LocalDoubleShare, SecureLocalDoubleShare}, - single_sharing::init_vdm, -}; +use super::local_double_share::{DoubleShares, LocalDoubleShare, SecureLocalDoubleShare}; use crate::runtime::sessions::large_session::LargeSessionHandles; use algebra::{ - matrix::MatrixMul, + matrix::VdmMatrix, structure_traits::{Derive, ErrorCorrect, Invert, Ring}, }; use async_trait::async_trait; use error_utils::anyhow_error_and_log; use itertools::Itertools; -use ndarray::{ArrayD, IxDyn}; use std::collections::HashMap; use threshold_types::protocol::ProtocolDescription; use threshold_types::role::Role; @@ -18,7 +14,12 @@ use tracing::instrument; pub type SecureDoubleSharing = RealDoubleSharing; -type DoubleArrayShares = (ArrayD, ArrayD); +/// One extraction step's worth of double-shares: the degree-t and degree-2t shares +/// gathered from every party at the same position `j`. +struct DoubleShareBatch { + deg_t: Vec, + deg_2t: Vec, +} pub struct DoubleShare { pub(crate) degree_t: Z, @@ -43,10 +44,10 @@ pub trait DoubleSharing: ProtocolDescription + Send + Sync + Clone { //as that'll influence how to reconstruct stuff later on pub struct RealDoubleSharing { local_double_share: S, - available_ldl: Vec>, + available_ldl: Vec>, available_shares: Vec<(Z, Z)>, max_num_iterations: usize, - vdm_matrix: ArrayD, + vdm_matrix: VdmMatrix, } impl ProtocolDescription for RealDoubleSharing { @@ -76,7 +77,7 @@ impl RealDoubleSharing { available_ldl: Vec::default(), available_shares: Vec::default(), max_num_iterations: usize::default(), - vdm_matrix: ArrayD::::default(IxDyn::default()), + vdm_matrix: VdmMatrix::default(), } } } @@ -113,11 +114,13 @@ impl DoubleSharing self.max_num_iterations = l; //Init vdm matrix only once or when dim changes - let shape = self.vdm_matrix.shape(); let curr_height = session.num_parties(); let curr_width = session.num_parties() - session.threshold() as usize; - if self.vdm_matrix.is_empty() || curr_height != shape[0] || curr_width != shape[1] { - self.vdm_matrix = init_vdm( + if self.vdm_matrix.is_empty() + || curr_height != self.vdm_matrix.height() + || curr_width != self.vdm_matrix.width() + { + self.vdm_matrix = VdmMatrix::from_exceptional_sequence( session.num_parties(), session.num_parties() - session.threshold() as usize, )?; @@ -168,47 +171,36 @@ impl DoubleSharing fn format_for_next( local_double_shares: HashMap>, l: usize, -) -> anyhow::Result>> { +) -> anyhow::Result>> { let num_parties = local_double_shares.len(); let mut res = Vec::with_capacity(l); for i in 0..l { - let mut vec_t = Vec::with_capacity(num_parties); - let mut vec_2t = Vec::with_capacity(num_parties); + let mut deg_t = Vec::with_capacity(num_parties); + let mut deg_2t = Vec::with_capacity(num_parties); for party_idx in 0..num_parties { let double_share_j = local_double_shares .get(&Role::indexed_from_zero(party_idx)) .ok_or_else(|| { anyhow_error_and_log(format!("Can not find shares for Party {}", party_idx + 1)) })?; - vec_t.push(double_share_j.share_t[i]); - vec_2t.push(double_share_j.share_2t[i]); + deg_t.push(double_share_j.share_t[i]); + deg_2t.push(double_share_j.share_2t[i]); } - res.push(( - ArrayD::from_shape_vec(IxDyn(&[num_parties]), vec_t)?.into_dyn(), - ArrayD::from_shape_vec(IxDyn(&[num_parties]), vec_2t)?.into_dyn(), - )); + res.push(DoubleShareBatch { deg_t, deg_2t }); } Ok(res) } /// Extract randomness of degree t and 2t using the parties' contributions and the VDM matrix fn compute_next_batch( - formatted_ldl: &mut Vec>, - vdm: &ArrayD, + formatted_ldl: &mut Vec>, + vdm: &VdmMatrix, ) -> anyhow::Result> { - let next_formatted_ldl = formatted_ldl + let batch = formatted_ldl .pop() .ok_or_else(|| anyhow_error_and_log("Can not access pop empty formatted_ldl vector"))?; - let res_t = next_formatted_ldl - .0 - .matmul(vdm)? - .into_raw_vec_and_offset() - .0; - let res_2t = next_formatted_ldl - .1 - .matmul(vdm)? - .into_raw_vec_and_offset() - .0; + let res_t = vdm.mul_vector(&batch.deg_t)?; + let res_2t = vdm.mul_vector(&batch.deg_2t)?; if res_t.len() != res_2t.len() { return Err(anyhow_error_and_log( "The size of the degree t and 2t vectors are not equal", diff --git a/core/threshold-execution/src/large_execution/single_sharing.rs b/core/threshold-execution/src/large_execution/single_sharing.rs index cf6b033be..b7bfb0ed6 100644 --- a/core/threshold-execution/src/large_execution/single_sharing.rs +++ b/core/threshold-execution/src/large_execution/single_sharing.rs @@ -1,13 +1,12 @@ use super::local_single_share::{LocalSingleShare, SecureLocalSingleShare}; use crate::runtime::sessions::large_session::LargeSessionHandles; use algebra::{ - matrix::MatrixMul, - structure_traits::{Derive, ErrorCorrect, Invert, Ring, RingWithExceptionalSequence}, + matrix::VdmMatrix, + structure_traits::{Derive, ErrorCorrect, Invert, Ring}, }; use async_trait::async_trait; use error_utils::anyhow_error_and_log; use itertools::Itertools; -use ndarray::{ArrayD, IxDyn}; use std::collections::HashMap; use threshold_types::protocol::ProtocolDescription; use threshold_types::role::Role; @@ -29,10 +28,10 @@ pub trait SingleSharing: ProtocolDescription + Send + Sync + Clone { //as that'll influence how to reconstruct stuff later on pub struct RealSingleSharing { local_single_share: S, - available_lsl: Vec>, + available_lsl: Vec>, available_shares: Vec, max_num_iterations: usize, - vdm_matrix: ArrayD, + vdm_matrix: VdmMatrix, } impl ProtocolDescription for RealSingleSharing { @@ -62,7 +61,7 @@ impl RealSingleSharing { available_lsl: Vec::default(), available_shares: Vec::default(), max_num_iterations: usize::default(), - vdm_matrix: ArrayD::::default(IxDyn::default()), + vdm_matrix: VdmMatrix::default(), } } } @@ -99,11 +98,13 @@ impl SingleSharing self.max_num_iterations = l; //Init vdm matrix only once or when dim changes - let shape = self.vdm_matrix.shape(); let curr_height = session.num_parties(); let curr_width = session.num_parties() - session.threshold() as usize; - if self.vdm_matrix.is_empty() || curr_height != shape[0] || curr_width != shape[1] { - self.vdm_matrix = init_vdm( + if self.vdm_matrix.is_empty() + || curr_height != self.vdm_matrix.height() + || curr_width != self.vdm_matrix.width() + { + self.vdm_matrix = VdmMatrix::from_exceptional_sequence( session.num_parties(), session.num_parties() - session.threshold() as usize, )?; @@ -130,43 +131,19 @@ impl SingleSharing } } -///Create the VDM matrix of dimension (height, width) such that -/// VDM_{i,j} = alpha_i^j, with alpha_i the ith element of the exceptional set -pub fn init_vdm( - height: usize, - width: usize, -) -> anyhow::Result> { - // We could actually probably take 0 in the VDM matrix, but to match the alpha indexing with the one we use for parties, - //we skip it - let exceptional_sequence: Vec = (0..height) - .map(|idx| Z::get_from_exceptional_sequence(idx + 1)) - .try_collect()?; - - let mut powers_of_exceptional_sequence = Vec::with_capacity(height * width); - for point in exceptional_sequence { - let mut power = Z::ONE; - for _ in 0..width { - powers_of_exceptional_sequence.push(power); - power *= point; - } - } - - Ok(ArrayD::from_shape_vec(IxDyn(&[height, width]), powers_of_exceptional_sequence)?.into_dyn()) -} - ///Have to be careful about ordering (e.g. cant just iterate over the set of key as its unordered) /// ///Format the map with keys role_i in Roles /// ///role_i -> [_{self}, ... , _{self}] /// -///to a vector appropriate fro the randomness extraction with keys j in [l] +///to a vector appropriate for the randomness extraction with keys j in [l] /// /// j -> [_{self}, ..., _{self}] fn format_for_next( local_single_shares: HashMap>, l: usize, -) -> anyhow::Result>> { +) -> anyhow::Result>> { let num_parties = local_single_shares.len(); let mut res = Vec::with_capacity(l); for i in 0..l { @@ -183,26 +160,24 @@ fn format_for_next( })?[i], ); } - res.push(ArrayD::from_shape_vec(IxDyn(&[num_parties]), vec)?.into_dyn()); + res.push(vec); } Ok(res) } ///Extract randomness using the parties contributions and the VDM matrix fn compute_next_batch( - formatted_lsl: &mut Vec>, - vdm: &ArrayD, + formatted_lsl: &mut Vec>, + vdm: &VdmMatrix, ) -> anyhow::Result> { - let res = formatted_lsl + let shares = formatted_lsl .pop() - .ok_or_else(|| anyhow_error_and_log("Can not pop empty formatted_lsl vector"))? - .matmul(vdm)?; - Ok(res.into_raw_vec_and_offset().0) + .ok_or_else(|| anyhow_error_and_log("Can not pop empty formatted_lsl vector"))?; + vdm.mul_vector(&shares) } #[cfg(test)] pub(crate) mod tests { - use super::init_vdm; use crate::large_execution::constants::DISPUTE_STAT_SEC; use crate::runtime::sessions::base_session::GenericBaseSessionHandles; use crate::runtime::sessions::session_parameters::GenericParameterHandles; @@ -212,7 +187,9 @@ pub(crate) mod tests { runtime::sessions::large_session::LargeSession, }; use algebra::galois_rings::degree_4::{ResiduePolyF4Z64, ResiduePolyF4Z128}; - use algebra::galois_rings::degree_8::ResiduePolyF8; + use algebra::galois_rings::degree_8::{ResiduePolyF8, ResiduePolyF8Z128}; + use algebra::matrix::VdmMatrix; + use algebra::structure_traits::{One, Zero}; use algebra::{ sharing::{ shamir::{RevealOp, ShamirSharings}, @@ -220,7 +197,6 @@ pub(crate) mod tests { }, structure_traits::{Derive, ErrorCorrect, Invert, Ring, Sample}, }; - use ndarray::Ix2; use num_integer::div_ceil; use rstest::rstest; use std::num::Wrapping; @@ -370,8 +346,8 @@ pub(crate) mod tests { #[test] fn test_vdm() { - let vdm = init_vdm(4, 4).unwrap(); - let coefs = vec![ + let vdm = VdmMatrix::::from_exceptional_sequence(4, 4).unwrap(); + let coefs: Vec = vec![ ResiduePolyF8 { coefs: [ Wrapping(1_u128), @@ -566,10 +542,13 @@ pub(crate) mod tests { }, //X^6 ]; - let vdm = vdm.into_dimensionality::().unwrap(); for i in 0..4 { + let mut row_selector = vec![ResiduePolyF8Z128::ZERO; 4]; + row_selector[i] = ResiduePolyF8Z128::ONE; + let row = vdm.mul_vector(&row_selector).unwrap(); + for j in 0..4 { - assert_eq!(coefs[4 * i + j], vdm[(i, j)]); + assert_eq!(coefs[4 * i + j], row[j]); } } } diff --git a/core/threshold-execution/src/small_execution/prss.rs b/core/threshold-execution/src/small_execution/prss.rs index 003c115be..a7a678a03 100644 --- a/core/threshold-execution/src/small_execution/prss.rs +++ b/core/threshold-execution/src/small_execution/prss.rs @@ -10,10 +10,7 @@ use crate::{ { communication::broadcast::{Broadcast, SyncReliableBroadcast}, constants::{PRSS_SIZE_MAX, STATSEC}, - large_execution::{ - single_sharing::init_vdm, - vss::{SecureVss, Vss}, - }, + large_execution::vss::{SecureVss, Vss}, runtime::sessions::{ base_session::BaseSessionHandles, session_parameters::ParameterHandles, }, @@ -21,14 +18,13 @@ use crate::{ }, }; use algebra::{ - matrix::{MatrixMul, compute_powers_list}, + matrix::{VdmMatrix, compute_powers_list}, poly::Poly, structure_traits::{ErrorCorrect, Invert, Ring, RingWithExceptionalSequence}, }; use anyhow::Context; use error_utils::{anyhow_error_and_log, log_error_wrapper}; use itertools::Itertools; -use ndarray::{ArrayD, IxDyn}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -293,13 +289,12 @@ impl::from_exceptional_sequence(n, n - t)?; for i in 0..c { //Retrieve the ith VSSed contribution of all parties let vss_s = vss_res.iter().map(|s| s[i]).collect_vec(); //Apply randomness extraction - let random_val = m_inverse.matmul(&ArrayD::from_shape_vec(IxDyn(&[n]), vss_s)?)?; - to_open.append(&mut random_val.into_raw_vec_and_offset().0); + to_open.append(&mut vdm.mul_vector(&vss_s)?); } // create all the subsets A that contain the party id @@ -1115,20 +1110,6 @@ impl DerivePRSSState< } } -/// Compute the transposed Vandermonde matrix with a_i = embed(i). -/// That is: -/// 1 1 1 ... 1 -/// a_1 a_2 a_3 ... a_columns -/// a_1^2 a_2^2 a_3^2 ... a_columns^2 -/// ... -/// a_1^{rows-1} a_2^{rows-1} a_3^{rows-1}... a_columns^{rows-1} -fn transpose_vdm( - rows: usize, - columns: usize, -) -> anyhow::Result> { - Ok(init_vdm::(columns, rows)?.reversed_axes()) -} - pub(crate) fn create_sets(all_roles: &[Role], t: usize) -> Vec> { let n = all_roles.len(); all_roles.iter().copied().combinations(n - t).collect() @@ -2541,53 +2522,19 @@ mod tests { } #[test] - fn test_vdm_inverse() { - let res = transpose_vdm(3, 4).unwrap(); - // Check first row is - // 1, 1, 1, 1 - assert_eq!(ResiduePolyF4::ONE, res[[0, 0]]); - assert_eq!(ResiduePolyF4::ONE, res[[0, 1]]); - assert_eq!(ResiduePolyF4::ONE, res[[0, 2]]); - assert_eq!(ResiduePolyF4::ONE, res[[0, 3]]); - // Check second row is - // 1, 2, 3, 4 = 1, x, 1+x, 2x - assert_eq!( - ResiduePolyF4::get_from_exceptional_sequence(1).unwrap(), - res[[1, 0]] - ); - assert_eq!( - ResiduePolyF4::get_from_exceptional_sequence(2).unwrap(), - res[[1, 1]] - ); - assert_eq!( - ResiduePolyF4::get_from_exceptional_sequence(3).unwrap(), - res[[1, 2]] - ); - assert_eq!( - ResiduePolyF4::get_from_exceptional_sequence(4).unwrap(), - res[[1, 3]] - ); - // Check third row is - // 1, x^2, (1+x)^2, (2x)^2 - assert_eq!( - ResiduePolyF4::get_from_exceptional_sequence(1).unwrap(), - res[[2, 0]] - ); - assert_eq!( - ResiduePolyF4Z128::get_from_exceptional_sequence(2).unwrap() - * ResiduePolyF4Z128::get_from_exceptional_sequence(2).unwrap(), - res[[2, 1]] - ); - assert_eq!( - ResiduePolyF4Z128::get_from_exceptional_sequence(3).unwrap() - * ResiduePolyF4Z128::get_from_exceptional_sequence(3).unwrap(), - res[[2, 2]] - ); - assert_eq!( - ResiduePolyF4Z128::get_from_exceptional_sequence(4).unwrap() - * ResiduePolyF4Z128::get_from_exceptional_sequence(4).unwrap(), - res[[2, 3]] - ); + fn test_vdm_matrix() { + let vdm = VdmMatrix::::from_exceptional_sequence(4, 3).unwrap(); + + for row_idx in 0..4 { + let alpha = ResiduePolyF4Z128::get_from_exceptional_sequence(row_idx + 1).unwrap(); + let mut row_selector = vec![ResiduePolyF4Z128::ZERO; 4]; + row_selector[row_idx] = ResiduePolyF4Z128::ONE; + let row = vdm.mul_vector(&row_selector).unwrap(); + + assert_eq!(ResiduePolyF4Z128::ONE, row[0]); + assert_eq!(alpha, row[1]); + assert_eq!(alpha * alpha, row[2]); + } } /// Test that compute_result fails as expected when a set is not present in the `true_psi_vals` given as input