diff --git a/.gitignore b/.gitignore index b72a2fec2..10e65d845 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .claude/settings.local.json ./CLAUDE.local.md +.codex /target* **/temp/* /keys diff --git a/core/experiments/benches/algebra.rs b/core/experiments/benches/algebra.rs index db44ec923..2bcf5128e 100644 --- a/core/experiments/benches/algebra.rs +++ b/core/experiments/benches/algebra.rs @@ -1,6 +1,6 @@ use aes_prng::AesRng; use algebra::poly::Poly; -use algebra::poly::lagrange_interpolation; +use algebra::poly::lagrange_interpolation_with_polys; use algebra::poly::lagrange_polynomials; use algebra::sharing::shamir::InputOp; use algebra::sharing::shamir::ShamirSharings; @@ -23,32 +23,27 @@ fn bench_lagrange_poly(c: &mut Criterion) { let p_str = format!("n:{num_parties}/t:{threshold}/e:{max_err}"); assert!(num_parties >= (threshold + 1) + 2 * max_err); + let mut rng = AesRng::seed_from_u64(0); + let secret = LevelOne::from_u128(2345); + let sharing = ShamirSharings::share(&mut rng, secret, num_parties, threshold).unwrap(); + let xs: Vec<_> = sharing + .shares + .iter() + .map(|s| LevelOne::from_u128(s.owner().one_based() as u128)) + .collect(); + let ys: Vec<_> = sharing.shares.iter().map(|s| s.value()).collect(); + + // Pre-compute Lagrange polynomials for the memoized benchmark + let lagrange_polys = lagrange_polynomials(&xs); + group.bench_function(BenchmarkId::new("lagrange_mem", p_str.clone()), |b| { - let mut rng = AesRng::seed_from_u64(0); - let secret = LevelOne::from_u128(2345); - let sharing = ShamirSharings::share(&mut rng, secret, num_parties, threshold).unwrap(); - let xs: Vec<_> = sharing - .shares - .iter() - .map(|s| LevelOne::from_u128(s.owner().one_based() as u128)) - .collect(); - let ys: Vec<_> = sharing.shares.iter().map(|s| s.value()).collect(); b.iter(|| { - let interpolated = lagrange_interpolation(&xs, &ys); + let interpolated = lagrange_interpolation_with_polys(&lagrange_polys, &ys); assert_eq!(interpolated.unwrap().eval(&LevelOne::from_u128(0)), secret); }); }); group.bench_function(BenchmarkId::new("lagrange_no_mem", p_str), |b| { - let mut rng = AesRng::seed_from_u64(0); - let secret = LevelOne::from_u128(2345); - let sharing = ShamirSharings::share(&mut rng, secret, num_parties, threshold).unwrap(); - let xs: Vec<_> = sharing - .shares - .iter() - .map(|s| LevelOne::from_u128(s.owner().one_based() as u128)) - .collect(); - let ys: Vec<_> = sharing.shares.iter().map(|s| s.value()).collect(); b.iter(|| { let ls = lagrange_polynomials(&xs); let mut res = Poly::zero(); diff --git a/core/experiments/src/choreography/server.rs b/core/experiments/src/choreography/server.rs index 1461abf0d..0f246808d 100644 --- a/core/experiments/src/choreography/server.rs +++ b/core/experiments/src/choreography/server.rs @@ -1,11 +1,12 @@ use crate::conf::party::PartyConf; use algebra::{ base_ring::{Z64, Z128}, + galois_fields::lagrange::init_lagrange_stores, galois_rings::common::ResiduePoly, structure_traits::{Derive, ErrorCorrect, Invert, Solve, Syndrome}, }; use observability::telemetry::make_span; -use std::sync::Arc; +use std::{num::NonZero, sync::Arc}; use threshold_execution::online::preprocessing::{ PreprocessorFactory, create_memory_factory, create_redis_factory, }; @@ -34,6 +35,15 @@ where ResiduePoly: Syndrome + ErrorCorrect + Invert + Solve + Derive, { let my_role: Role = settings.protocol().host().into(); + if let Some(peers) = settings.protocol().peers() { + let num_parties = peers.len() + 1; + // The threshold-fhe config currently assumes n = 3t + 1. + let threshold = (num_parties - 1) / 3; + init_lagrange_stores( + NonZero::new(num_parties).expect("num_parties must be non-zero"), + threshold, + )?; + } let tls_conf = settings .certpaths diff --git a/core/service/src/bin/kms-server.rs b/core/service/src/bin/kms-server.rs index 9c2577204..a1d5e336f 100644 --- a/core/service/src/bin/kms-server.rs +++ b/core/service/src/bin/kms-server.rs @@ -1,3 +1,4 @@ +use algebra::galois_fields::lagrange::init_lagrange_stores; use anyhow::ensure; use clap::Parser; use futures_util::future::OptionFuture; @@ -32,7 +33,7 @@ use kms_lib::{ }, }, }; -use std::{net::ToSocketAddrs, sync::Arc, thread}; +use std::{net::ToSocketAddrs, num::NonZero, sync::Arc, thread}; use thread_handles::init_rayon_thread_pool; use threshold_networking::tls::AttestedVerifier; use tokio::net::TcpListener; @@ -367,6 +368,19 @@ async fn main_exec() -> anyhow::Result<()> { tracing::info!("Starting KMS Server with core config: {:?}", &core_config); + // NOTE: Cache for GF16 (which we use here -- for now) is fully filled. + // The call below is effectively a no-op unless we work in bigger fields. + if let Some(threshold_config) = core_config.threshold.as_ref() + && let Some(peers) = threshold_config.peers.as_ref() + && !peers.is_empty() + { + init_lagrange_stores( + NonZero::new(peers.len()) + .expect("peers.len() (i.e. number of parties) was just checked to be non-zero"), + threshold_config.threshold as usize, + )?; + } + tracing::info!( "Multi-threading values: tokio::num_workers: {}, rayon_num_threads: {}, total_num_cpus: {}", tokio::runtime::Handle::current().metrics().num_workers(), diff --git a/core/threshold-algebra/src/galois_fields/common.rs b/core/threshold-algebra/src/galois_fields/common.rs deleted file mode 100644 index f1f094559..000000000 --- a/core/threshold-algebra/src/galois_fields/common.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::{sharing::shamir::ShamirFieldPoly, structure_traits::Field, syndrome::decode_syndrome}; - -pub fn syndrome_decoding_z2>( - parties: &[usize], - syndrome: &ShamirFieldPoly, - threshold: usize, -) -> Vec { - let xs: Vec = parties.iter().map(|s| F::from(*s as u8)).collect(); - let r = parties.len() - (threshold + 1); - decode_syndrome(syndrome, &xs, r) -} diff --git a/core/threshold-algebra/src/galois_fields/gf128.rs b/core/threshold-algebra/src/galois_fields/gf128.rs index 47019e05f..d7bc03888 100644 --- a/core/threshold-algebra/src/galois_fields/gf128.rs +++ b/core/threshold-algebra/src/galois_fields/gf128.rs @@ -1,16 +1,11 @@ -use std::collections::HashMap; - -use error_utils::anyhow_error_and_log; - -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; - use crate::{ + galois_fields::LagrangeMap, poly::Poly, structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::sync::{LazyLock, OnceLock}; g2p!( GF128, @@ -89,32 +84,11 @@ impl RingWithExceptionalSequence for GF128 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +pub(crate) static LAGRANGE_STORE: OnceLock> = OnceLock::new(); impl Field for GF128 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get()?.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/gf16.rs b/core/threshold-algebra/src/galois_fields/gf16.rs index 823e14e78..2a686dff3 100644 --- a/core/threshold-algebra/src/galois_fields/gf16.rs +++ b/core/threshold-algebra/src/galois_fields/gf16.rs @@ -1,14 +1,11 @@ -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; -use error_utils::anyhow_error_and_log; -use std::collections::HashMap; - use crate::{ + galois_fields::{LagrangeMap, lagrange::build_lagrange_map}, poly::Poly, structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::{num::NonZero, sync::LazyLock}; g2p!( GF16, @@ -87,32 +84,17 @@ impl RingWithExceptionalSequence for GF16 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +/// Pre-computed Lagrange basis for all (ordered) non-empty subsets of GF16 that exclude 0. +/// Size is \sum_{k=1}^{15} C(15, k) * k = 15 * 2^14 = 245760, which is small enough to be pre-computed and stored in memory. +pub(crate) static LAGRANGE_STORE: LazyLock> = LazyLock::new(|| { + build_lagrange_map::(NonZero::new(15).expect("15 is non-zero"), 0).expect( + "Initialization of the GF16 Lagrange basis can't fail with 15 parties and threshold 0", + ) +}); impl Field for GF16 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/gf256.rs b/core/threshold-algebra/src/galois_fields/gf256.rs index d06070ee4..7f61618a9 100644 --- a/core/threshold-algebra/src/galois_fields/gf256.rs +++ b/core/threshold-algebra/src/galois_fields/gf256.rs @@ -1,14 +1,11 @@ -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; -use error_utils::anyhow_error_and_log; -use std::collections::HashMap; - use crate::{ + galois_fields::LagrangeMap, poly::Poly, structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::sync::{LazyLock, OnceLock}; g2p!( GF256, @@ -87,32 +84,11 @@ impl RingWithExceptionalSequence for GF256 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +pub(crate) static LAGRANGE_STORE: OnceLock> = OnceLock::new(); impl Field for GF256 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get()?.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/gf32.rs b/core/threshold-algebra/src/galois_fields/gf32.rs index 42ee806af..9c68b9af6 100644 --- a/core/threshold-algebra/src/galois_fields/gf32.rs +++ b/core/threshold-algebra/src/galois_fields/gf32.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; - -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; use crate::{ + galois_fields::LagrangeMap, poly::Poly, structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; -use error_utils::anyhow_error_and_log; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::sync::{LazyLock, OnceLock}; g2p!( GF32, @@ -87,32 +84,11 @@ impl RingWithExceptionalSequence for GF32 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +pub(crate) static LAGRANGE_STORE: OnceLock> = OnceLock::new(); impl Field for GF32 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get()?.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/gf64.rs b/core/threshold-algebra/src/galois_fields/gf64.rs index e1c4b46f3..5239ff913 100644 --- a/core/threshold-algebra/src/galois_fields/gf64.rs +++ b/core/threshold-algebra/src/galois_fields/gf64.rs @@ -1,16 +1,11 @@ -use std::collections::HashMap; - -use error_utils::anyhow_error_and_log; - -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; - use crate::{ + galois_fields::LagrangeMap, poly::Poly, structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::sync::{LazyLock, OnceLock}; g2p!( GF64, @@ -89,32 +84,11 @@ impl RingWithExceptionalSequence for GF64 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +pub(crate) static LAGRANGE_STORE: OnceLock> = OnceLock::new(); impl Field for GF64 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get()?.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/gf8.rs b/core/threshold-algebra/src/galois_fields/gf8.rs index df0ca2969..868f4b91b 100644 --- a/core/threshold-algebra/src/galois_fields/gf8.rs +++ b/core/threshold-algebra/src/galois_fields/gf8.rs @@ -1,16 +1,11 @@ -use std::collections::HashMap; - -use crate::structure_traits::RingWithExceptionalSequence; -use crate::{galois_fields::LagrangeMap, poly::lagrange_polynomials}; -use error_utils::anyhow_error_and_log; - use crate::{ + galois_fields::{LagrangeMap, lagrange::build_lagrange_map}, poly::Poly, - structure_traits::{Field, FromU128, One, Ring, Sample, Zero}, + structure_traits::{Field, FromU128, One, Ring, RingWithExceptionalSequence, Sample, Zero}, }; use g2p::{GaloisField, g2p}; use serde::{Deserialize, Serialize}; -use std::sync::{LazyLock, RwLock}; +use std::{num::NonZero, sync::LazyLock}; g2p!( GF8, @@ -89,32 +84,17 @@ impl RingWithExceptionalSequence for GF8 { } } -static LAGRANGE_STORE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +/// Pre-computed Lagrange basis for all (ordered) non-empty subsets of GF8 that exclude 0. +/// Size is \sum_{k=1}^{7} C(7, k) * k = 7 * 2^6 = 448, which is small enough to be pre-computed and stored in memory. +pub(crate) static LAGRANGE_STORE: LazyLock> = LazyLock::new(|| { + build_lagrange_map::(NonZero::new(7).expect("7 is non-zero"), 0).expect( + "Initialization of the GF8 Lagrange basis can't fail with 7 parties and threshold 0", + ) +}); impl Field for GF8 { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = LAGRANGE_STORE.read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = LAGRANGE_STORE.write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]> { + LAGRANGE_STORE.get(points).map(|v| v.as_slice()) } } diff --git a/core/threshold-algebra/src/galois_fields/lagrange.rs b/core/threshold-algebra/src/galois_fields/lagrange.rs new file mode 100644 index 000000000..aa8eebfea --- /dev/null +++ b/core/threshold-algebra/src/galois_fields/lagrange.rs @@ -0,0 +1,55 @@ +use std::num::NonZero; + +use super::LagrangeMap; +use crate::{poly::lagrange_polynomials, structure_traits::Field}; +use itertools::Itertools; + +/// Builds a LagrangeMap containing pre-computed Lagrange polynomials for all sorted subsets +/// of `{embed(1), ..., embed(num_parties)}` with size ≥ `min_threshold + 1`. +pub(crate) fn build_lagrange_map( + num_parties: NonZero, + min_threshold: usize, +) -> anyhow::Result> { + let num_parties = num_parties.get(); + let all_points: Vec = (1..=num_parties) + .map(F::get_from_exceptional_sequence) + .collect::>>()?; + let mut map = LagrangeMap::new(); + for size in (min_threshold + 1)..=num_parties { + for subset in all_points.iter().combinations(size) { + let points: Vec = subset.into_iter().copied().collect(); + let polys = lagrange_polynomials(&points); + map.insert(points, polys); + } + } + Ok(map) +} + +#[allow(unused_variables)] +/// Pre-computes Lagrange polynomial stores for all enabled Galois field types. +/// Must be called at startup with the known number of parties and threshold. +/// +/// __NOTE__: GF8 and GF16 are small enough that we can pre-compute all possible lagrange basis. +/// so they are skipped here. +pub fn init_lagrange_stores( + num_parties: NonZero, + min_threshold: usize, +) -> anyhow::Result<()> { + #[cfg(feature = "extension_degree_5")] + super::gf32::LAGRANGE_STORE + .set(build_lagrange_map(num_parties, min_threshold)?) + .ok(); + #[cfg(feature = "extension_degree_6")] + super::gf64::LAGRANGE_STORE + .set(build_lagrange_map(num_parties, min_threshold)?) + .ok(); + #[cfg(feature = "extension_degree_7")] + super::gf128::LAGRANGE_STORE + .set(build_lagrange_map(num_parties, min_threshold)?) + .ok(); + #[cfg(feature = "extension_degree_8")] + super::gf256::LAGRANGE_STORE + .set(build_lagrange_map(num_parties, min_threshold)?) + .ok(); + Ok(()) +} diff --git a/core/threshold-algebra/src/galois_fields/mod.rs b/core/threshold-algebra/src/galois_fields/mod.rs index ed08365e7..c6b704973 100644 --- a/core/threshold-algebra/src/galois_fields/mod.rs +++ b/core/threshold-algebra/src/galois_fields/mod.rs @@ -9,7 +9,6 @@ ))] pub(crate) type LagrangeMap = std::collections::HashMap, Vec>>; -pub mod common; #[cfg(feature = "extension_degree_7")] pub mod gf128; #[cfg(feature = "extension_degree_4")] @@ -22,3 +21,12 @@ pub mod gf32; pub mod gf64; #[cfg(feature = "extension_degree_3")] pub mod gf8; +#[cfg(any( + feature = "extension_degree_3", + feature = "extension_degree_4", + feature = "extension_degree_5", + feature = "extension_degree_6", + feature = "extension_degree_7", + feature = "extension_degree_8", +))] +pub mod lagrange; diff --git a/core/threshold-algebra/src/galois_rings/common.rs b/core/threshold-algebra/src/galois_rings/common.rs index 3938d7145..4f893428d 100644 --- a/core/threshold-algebra/src/galois_rings/common.rs +++ b/core/threshold-algebra/src/galois_rings/common.rs @@ -7,7 +7,7 @@ use crate::{ BaseRing, Derive, FromU128, Invert, One, QuotientMaximalIdeal, Ring, RingWithExceptionalSequence, Sample, Solve, Solve1, Syndrome, ZConsts, Zero, }, - syndrome::lagrange_numerators, + syndrome::{lagrange_numerators, syndrome_decoding_z2}, }; use error_utils::anyhow_error_and_log; @@ -17,7 +17,6 @@ use threshold_types::role::Role; use crate::{ base_ring::{Z64, Z128}, - galois_fields::common::syndrome_decoding_z2, sharing::shamir::{ShamirFieldPoly, ShamirSharings}, sharing::share::Share, }; diff --git a/core/threshold-algebra/src/poly.rs b/core/threshold-algebra/src/poly.rs index e041cec9d..e03e222ba 100644 --- a/core/threshold-algebra/src/poly.rs +++ b/core/threshold-algebra/src/poly.rs @@ -600,15 +600,27 @@ pub fn lagrange_polynomials(points: &[F]) -> Vec> { /// interpolate a polynomial through coordinates where points holds the x-coordinates and values holds the y-coordinates pub fn lagrange_interpolation(points: &[F], values: &[F]) -> anyhow::Result> { - let ls = F::memoize_lagrange(points)?; - if ls.len() != values.len() { + if let Some(cached) = F::cached_lagrange_polys(points) { + lagrange_interpolation_with_polys(cached, values) + } else { + lagrange_interpolation_with_polys(lagrange_polynomials(points), values) + } +} + +/// interpolate a polynomial using pre-computed Lagrange basis polynomials and y-coordinates +pub fn lagrange_interpolation_with_polys( + lagrange_polys: impl AsRef<[Poly]>, + values: &[F], +) -> anyhow::Result> { + let lagrange_polys = lagrange_polys.as_ref(); + if lagrange_polys.len() != values.len() { return Err(anyhow_error_and_log( "Lagrange interpolation failure: mismatch between number of points and values" .to_string(), )); } let mut res = Poly::zero(); - for (li, vi) in ls.into_iter().zip_eq(values.iter()) { + for (li, vi) in lagrange_polys.iter().zip_eq(values.iter()) { let term = li * vi; res = res + term; } diff --git a/core/threshold-algebra/src/structure_traits.rs b/core/threshold-algebra/src/structure_traits.rs index bb51b47d7..dd64df0e7 100644 --- a/core/threshold-algebra/src/structure_traits.rs +++ b/core/threshold-algebra/src/structure_traits.rs @@ -92,7 +92,9 @@ pub trait Field where Self: RingWithExceptionalSequence + Div + DivAssign, { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>>; + /// Looks up pre-computed Lagrange polynomials for the given point set. + /// Returns `None` if the store has not been initialized. + fn cached_lagrange_polys(points: &[Self]) -> Option<&'static [Poly]>; /// computes the multiplicative inverse of the field element fn invert(&self) -> Self { diff --git a/core/threshold-algebra/src/syndrome.rs b/core/threshold-algebra/src/syndrome.rs index 3de9badba..784ba2e4f 100644 --- a/core/threshold-algebra/src/syndrome.rs +++ b/core/threshold-algebra/src/syndrome.rs @@ -1,6 +1,7 @@ use super::{ bivariate::compute_powers_list, poly::Poly, + sharing::shamir::ShamirFieldPoly, structure_traits::{Field, Ring}, }; use std::ops::Neg; @@ -167,6 +168,17 @@ pub fn decode_syndrome(syndrome: &Poly, x_alpha: &[F], r: usize) -> e } +/// Decodes a syndrome over a binary extension field using party indices as evaluation points. +pub fn syndrome_decoding_z2>( + parties: &[usize], + syndrome: &ShamirFieldPoly, + threshold: usize, +) -> Vec { + let xs: Vec = parties.iter().map(|s| F::from(*s as u8)).collect(); + let r = parties.len() - (threshold + 1); + decode_syndrome(syndrome, &xs, r) +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/threshold-bgv/src/algebra/levels.rs b/core/threshold-bgv/src/algebra/levels.rs index 25a7335eb..504513151 100644 --- a/core/threshold-bgv/src/algebra/levels.rs +++ b/core/threshold-bgv/src/algebra/levels.rs @@ -1,7 +1,7 @@ use algebra::{ PRSSConversions, error_correction::error_correction, - poly::{Poly, lagrange_polynomials}, + poly::Poly, sharing::{shamir::ShamirSharings, share::Share}, structure_traits::{ ErrorCorrect, Field, FromU128, Invert, One, Ring, RingWithExceptionalSequence, Sample, @@ -21,11 +21,8 @@ use itertools::Itertools; use rand::CryptoRng; use rand::Rng; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -use std::sync::LazyLock; -use std::sync::RwLock; use crate::algebra::crt::LevelKswCrtRepresentation; use crate::algebra::crt::from_crt; @@ -349,32 +346,11 @@ macro_rules! impl_field_level { } } - static []: LazyLock, Vec>>>> = - LazyLock::new(|| RwLock::new(HashMap::new())); - impl Field for $name { - fn memoize_lagrange(points: &[Self]) -> anyhow::Result>> { - if let Ok(lock_lagrange_store) = [].read() { - match lock_lagrange_store.get(points) { - Some(v) => Ok(v.clone()), - None => { - drop(lock_lagrange_store); - if let Ok(mut lock_lagrange_store) = [].write() { - let lagrange_pols = lagrange_polynomials(points); - lock_lagrange_store.insert(points.to_vec(), lagrange_pols.clone()); - Ok(lagrange_pols) - } else { - Err(anyhow_error_and_log( - "Error writing LAGRANGE_STORE".to_string(), - )) - } - } - } - } else { - Err(anyhow_error_and_log( - "Error reading LAGRANGE_STORE".to_string(), - )) - } + fn cached_lagrange_polys(_points: &[Self]) -> Option<&'static [Poly]> { + // BGV level types are experimental; no pre-computed store. + // Falls back to direct computation in lagrange_interpolation. + None } fn invert(&self) -> Self {