├── .gitignore ├── src ├── pvw │ ├── proto │ │ ├── mod.rs │ │ └── pvw.proto │ ├── mod.rs │ └── pvw.rs ├── lib.rs ├── server │ ├── mod.rs │ ├── powers_x.rs │ ├── pvw_decrypt.rs │ ├── range_fn │ │ ├── range_fn_fma.rs │ │ └── mod.rs │ └── phase2.rs ├── plaintext.rs ├── optimised.rs ├── preprocessing.rs ├── utils.rs ├── client.rs └── main.rs ├── scripts └── run_many.sh ├── setup.sh ├── benches ├── pvw.rs ├── basic_ops.rs └── range_fn.rs ├── LICENSE ├── Cargo.toml ├── Security.md ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /data -------------------------------------------------------------------------------- /src/pvw/proto/mod.rs: -------------------------------------------------------------------------------- 1 | // include protos 2 | include!(concat!(env!("OUT_DIR"), "/_.rs")); 3 | -------------------------------------------------------------------------------- /src/pvw/mod.rs: -------------------------------------------------------------------------------- 1 | mod proto; 2 | mod pvw; 3 | 4 | pub use proto::PvwCiphertext as PvwCiphertextProto; 5 | pub use pvw::*; 6 | -------------------------------------------------------------------------------- /src/pvw/proto/pvw.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message PvwCiphertext { 4 | bytes b = 1; 5 | bytes a = 2; 6 | } -------------------------------------------------------------------------------- /scripts/run_many.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export RUSTFLAGS=-Awarnings 3 | for x in {0..15} ; do 4 | cargo run --release --features "level" 1 5 | done -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # clean 4 | if [ -d "./data" ]; then 5 | rm -rf ./data 6 | fi 7 | 8 | export RUSTFLAGS=-Awarnings 9 | 10 | cargo test --release utils::tests::test_store_range_coeffs 11 | cargo test --release utils::tests::generate_and_store_random_clues 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod optimised; 3 | pub mod plaintext; 4 | pub mod preprocessing; 5 | pub mod pvw; 6 | pub mod server; 7 | pub mod utils; 8 | 9 | // Fixing a few constants here. They can modified 10 | // later or probably varied across runs 11 | pub const GAMMA: usize = 5; 12 | pub const MESSAGE_BYTES: usize = 512; 13 | pub const K: usize = 64; // 64*2*256 = 32768 14 | pub const BUCKET_COUNT: usize = K * 2; 15 | pub const BUCKET_SIZE: usize = (MESSAGE_BYTES / 2); // since each lane fits 2 bytes 16 | -------------------------------------------------------------------------------- /benches/pvw.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use omr::pvw::{PvwParameters, PvwSecretKey}; 3 | use rand::{thread_rng}; 4 | 5 | fn bench(c: &mut Criterion) { 6 | let mut group = c.benchmark_group("pvw"); 7 | 8 | let mut rng = thread_rng(); 9 | let params = PvwParameters::default(); 10 | let sk = PvwSecretKey::random(¶ms, &mut rng); 11 | let m = vec![0, 0, 0, 0]; 12 | 13 | group.bench_function("gen_public_key", |b| { 14 | b.iter(|| { 15 | let _ = sk.public_key(&mut rng); 16 | }); 17 | }); 18 | 19 | let pk = sk.public_key(&mut rng); 20 | group.bench_function("encrypt", |b| { 21 | b.iter(|| { 22 | let _ = pk.encrypt(&m, &mut rng); 23 | }); 24 | }); 25 | } 26 | 27 | criterion_group!(pvw, bench); 28 | criterion_main!(pvw); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JANMAJAYA MALL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "omr" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | itertools = "0.10.5" 11 | ndarray = {version = "0.15.6", features = ["rayon"]} 12 | rand_chacha = "0.3.1" 13 | statrs = "0.16.0" 14 | num-bigint-dig = {version = "0.8.2", features = ["prime"]} 15 | num-traits = "0.2.15" 16 | byteorder = "1.4.3" 17 | rayon = "1.7.0" 18 | prost = "0.11" 19 | bytes = "1.4.0" 20 | walkdir = "2.3.3" 21 | traits = {git = "https://github.com/Janmajayamall/bfv.git", branch = "main"} 22 | 23 | [target.'cfg(not(target_arch = "x86_64"))'.dependencies] 24 | bfv = {git = "https://github.com/Janmajayamall/bfv.git", branch = "main", default-features = false, features = ["std", "serialize"]} 25 | 26 | [target.'cfg(target_arch = "x86_64")'.dependencies] 27 | bfv = {git = "https://github.com/Janmajayamall/bfv.git", branch = "main", default-features = false, features = ["hexl-ntt", "hexl", "serialize"]} 28 | hexl-rs = {git = "https://github.com/Janmajayamall/hexl-rs.git"} 29 | 30 | [build-dependencies] 31 | prost-build = "0.11.9" 32 | 33 | 34 | [features] 35 | level = [] 36 | noise = [] 37 | precomp_pvw = [] 38 | 39 | 40 | [dev-dependencies] 41 | criterion = "0.4" 42 | mimalloc-rust = "0.2.1" 43 | 44 | [[bench]] 45 | name = "basic_ops" 46 | harness = false 47 | 48 | [[bench]] 49 | name = "range_fn" 50 | harness = false 51 | 52 | [[bench]] 53 | name = "pvw" 54 | harness = false 55 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{level_down, print_noise}; 2 | use bfv::{Ciphertext, EvaluationKey, Evaluator, SecretKey}; 3 | 4 | pub mod phase2; 5 | pub mod powers_x; 6 | pub mod pvw_decrypt; 7 | pub mod range_fn; 8 | 9 | /// Mutliplies all ciphertexts output from range_fn together adn returns a single 10 | /// ciphertext in Coefficient representation. 11 | pub fn mul_and_reduce_ranged_cts_to_1( 12 | ranged_cts: &((Ciphertext, Ciphertext), (Ciphertext, Ciphertext)), 13 | evaluator: &Evaluator, 14 | ek: &EvaluationKey, 15 | sk: &SecretKey, 16 | ) -> Ciphertext { 17 | // Use binary tree multiplication to reduce multiplicative depth 18 | // ct[0] * ct[1] ct[2] * ct[4] 19 | // v0 * v1 20 | // v 21 | let (v0, v1) = rayon::join( 22 | || { 23 | let v0 = evaluator.mul(&ranged_cts.0 .0, &ranged_cts.0 .1); 24 | let mut v0 = evaluator.relinearize(&v0, &ek); 25 | 26 | level_down!( 27 | evaluator.mod_down_next(&mut v0); 28 | ); 29 | 30 | v0 31 | }, 32 | || { 33 | let v1 = evaluator.mul(&ranged_cts.1 .0, &ranged_cts.1 .1); 34 | let mut v1 = evaluator.relinearize(&v1, &ek); 35 | 36 | level_down!( 37 | evaluator.mod_down_next(&mut v1); 38 | ); 39 | 40 | v1 41 | }, 42 | ); 43 | 44 | print_noise!( 45 | println!("v0 noise: {}", evaluator.measure_noise(&sk, &v0)); 46 | println!("v1 noise: {}", evaluator.measure_noise(&sk, &v1)); 47 | ); 48 | 49 | let v = evaluator.mul(&v0, &v1); 50 | // Relinearization of `v` can be modified such that overall ntts can be minized. 51 | // We expect `v` to be in evaluation form. Thus we convert c0 and c1, not c2, to evaluation form 52 | // after scale_and_round. The we key_switch `c2` and obtain c0' and c1' in Evaluation representation. 53 | // Then we add c0 and c1 with c0' and c1' respectively without having to switching c0' and 54 | // c1' to Coefficient representation. This saves us 2 NTTs compared to what we do at present. 55 | let mut v = evaluator.relinearize(&v, &ek); 56 | 57 | v 58 | } 59 | -------------------------------------------------------------------------------- /Security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | > **Note:** Source code of OMR has not been audited and cannot guarantee security against implementation bugs. The security below is only concerned with parameter security of the schemes used in OMR. 4 | 5 | LWE parameters used by OMR has security of **122 bits** 6 | 7 | LWE parameters used by PVW scheme has security of **120 bits**. 8 | 9 | ## OMR security 10 | 11 | To verify the security of OMR you can use [lattice estimator](https://github.com/malb/lattice-estimator). 12 | 13 | The parameters we use for OMR are as following: 14 | 15 | $n = 2^{15}$
16 | $logQ = 760$
17 | $logP = 160$
18 | $logQP = 920$
19 | 20 | The secret key is sampled from sparse ternary distribution with hamming weight $n/2$. 21 | 22 | To check security we will be using the cost model used by [HES](http://homomorphicencryption.org/wp-content/uploads/2018/11/HomomorphicEncryptionStandardv1.1.pdf), that is `RC.BDGL16`. 23 | 24 | To verify the security, first install sage and clone lattice estimator. The run the following sage script: 25 | Warning: The script can take a few hours to finish. 26 | 27 | ``` 28 | from estimator import * 29 | 30 | omr920 = LWE.Parameters( 31 | n=2**15, 32 | q=2**920, 33 | Xs=ND.SparseTernary(2**15,2**13,2**13), 34 | Xe=ND.DiscreteGaussian(3.19), 35 | m=2**15, 36 | tag="OMR920", 37 | ) 38 | 39 | res = LWE.estimate(omr920, red_cost_model = RC.BDGL16) 40 | print(res) 41 | ``` 42 | 43 | The script should output the following output 44 | 45 | ``` 46 | usvp :: rop: ≈2^122.4, red: ≈2^122.4, δ: 1.004864, β: 298, d: 63052, tag: usvp 47 | bdd :: rop: ≈2^122.2, red: ≈2^122.1, svp: ≈2^117.1, β: 297, η: 345, d: 65384, tag: bdd 48 | bdd_hybrid :: rop: ≈2^122.2, red: ≈2^122.1, svp: ≈2^117.1, β: 297, η: 345, ζ: 0, |S|: 1, d: 65537, prob: 1, ↻: 1, tag: hybrid 49 | bdd_mitm_hybrid :: rop: ≈2^181.6, red: ≈2^180.6, svp: ≈2^180.6, β: 297, η: 2, ζ: 135, |S|: ≈2^180.2, d: 65402, prob: ≈2^-56.3, ↻: ≈2^58.5, tag: hybrid 50 | dual :: rop: ≈2^122.4, mem: ≈2^33.8, m: ≈2^15.0, β: 298, d: 65536, ↻: 1, tag: dual 51 | dual_hybrid :: rop: ≈2^122.4, mem: ≈2^106.2, m: ≈2^15.0, β: 298, d: 65475, ↻: 1, ζ: 61, tag: dual_hybrid 52 | ``` 53 | 54 | `rop` indicates estimated runtime of different LWE attacks on LWE parameters of OMR. As visible, for each attack `rop` is atleast 2^122. 55 | 56 | ## PVW Security 57 | 58 | TODO 59 | -------------------------------------------------------------------------------- /benches/basic_ops.rs: -------------------------------------------------------------------------------- 1 | use bfv::{Encoding, Evaluator, Modulus, PolyCache, PolyType, Representation, SecretKey}; 2 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 3 | use itertools::{izip, Itertools}; 4 | use ndarray::Array2; 5 | use omr::{ 6 | optimised::{coefficient_u128_to_ciphertext, fma_reverse_u128_vec, optimised_poly_fma}, 7 | utils::generate_bfv_parameters, 8 | }; 9 | use rand::thread_rng; 10 | 11 | fn bench(c: &mut Criterion) { 12 | let mut group = c.benchmark_group("basic-ops"); 13 | // group.measurement_time(Duration::new(100, 0)); 14 | // group.sample_size(10); 15 | 16 | let mut rng = thread_rng(); 17 | let params = generate_bfv_parameters(); 18 | let m = params 19 | .plaintext_modulus_op 20 | .random_vec(params.degree, &mut rng); 21 | let sk = SecretKey::random_with_params(¶ms, &mut rng); 22 | 23 | let evaluator = Evaluator::new(params); 24 | let pt = evaluator.plaintext_encode(&m, Encoding::simd(0, PolyCache::Mul(PolyType::Q))); 25 | let mut ct0 = evaluator.encrypt(&sk, &pt, &mut rng); 26 | evaluator.ciphertext_change_representation(&mut ct0, Representation::Evaluation); 27 | 28 | let cts = (0..256) 29 | .map(|_| { 30 | let mut c = evaluator.encrypt(&sk, &pt, &mut rng); 31 | evaluator.ciphertext_change_representation(&mut c, Representation::Evaluation); 32 | c 33 | }) 34 | .collect_vec(); 35 | 36 | // optimised 37 | let polys = vec![pt.mul_poly_ref().clone(); 256]; 38 | group.bench_function("optimised_fma", |b| { 39 | b.iter(|| { 40 | let ctx = evaluator.params().poly_ctx(&PolyType::Q, 0); 41 | let mut res00 = Array2::::zeros((ctx.moduli_count(), ctx.degree())); 42 | let mut res01 = Array2::::zeros((ctx.moduli_count(), ctx.degree())); 43 | optimised_poly_fma(&cts, &polys, &mut res00, &mut res01); 44 | let _ = coefficient_u128_to_ciphertext(evaluator.params(), &res00, &res01, 0); 45 | }); 46 | }); 47 | 48 | // unoptimised fma 49 | group.bench_function("unoptimised_fma", |b| { 50 | b.iter(|| { 51 | let mut res = evaluator.mul_poly(&cts[0], &polys[0]); 52 | izip!(cts.iter(), polys.iter()) 53 | .skip(1) 54 | .for_each(|(c, p)| evaluator.fma_poly(&mut res, c, p)); 55 | }); 56 | }); 57 | 58 | for degree in [1 << 15] { 59 | let modulus = Modulus::new(1125899904679937); 60 | let mut sum = vec![0; degree]; 61 | let a0 = modulus.random_vec(degree, &mut rng); 62 | let a1 = modulus.random_vec(degree, &mut rng); 63 | 64 | group.bench_function( 65 | BenchmarkId::new("fma_reverse_u128_vec", format!("n={degree}")), 66 | |b| { 67 | b.iter(|| { 68 | fma_reverse_u128_vec(&mut sum, &a0, &a1); 69 | }); 70 | }, 71 | ); 72 | } 73 | } 74 | 75 | criterion_group!(basic_ops, bench); 76 | criterion_main!(basic_ops); 77 | -------------------------------------------------------------------------------- /src/plaintext.rs: -------------------------------------------------------------------------------- 1 | use bfv::Modulus; 2 | use rayon::{ 3 | prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}, 4 | slice::ParallelSliceMut, 5 | }; 6 | 7 | use crate::utils::read_range_coeffs; 8 | 9 | fn evaluate_powers(start: usize, end: usize, calculated: &mut [u64], bases: bool) { 10 | // start is always a power of two. -1 is equal to mask. 11 | let mask = start - 1; 12 | 13 | // all values from start..start*2 require calculated value of start. So we calculated it here 14 | if !bases { 15 | calculated[start - 1] = calculated[start / 2 - 1] * calculated[start / 2 - 1]; 16 | } 17 | 18 | // split at start+1 to assure that mutated values are disjoint 19 | let (done, pending) = calculated.split_at_mut(start - 1 + 1); 20 | 21 | // we only operate on slice from start+1 till end-1 22 | let pending = &mut pending[..(end - 1 - start)]; 23 | let cores = 1; 24 | let size = (pending.len() as f64 / cores as f64).ceil() as usize; 25 | 26 | let pending_chunks = pending.par_chunks_mut(size); 27 | 28 | pending_chunks 29 | .enumerate() 30 | .into_par_iter() 31 | .for_each(|(index, chunk)| { 32 | chunk.iter_mut().enumerate().for_each(|(chunk_index, v)| { 33 | // calculate real_index of the value to figure out the mask 34 | let real_index = size * index + chunk_index + start + 1; 35 | *v = done.last().unwrap() * done[(real_index & mask) - 1]; 36 | }); 37 | }); 38 | } 39 | 40 | pub fn powers_of_x_modulus(x: u64, modq: &Modulus, degree: usize) -> Vec { 41 | let mut cache = vec![0u64; degree]; 42 | let mut calculated = vec![0u64; degree]; 43 | cache[0] = x; 44 | calculated[0] = 1; 45 | let mut mul_count = 0; 46 | for i in (0..(degree + 1)).rev() { 47 | let mut exp = i; 48 | let mut base = 1usize; 49 | let mut res = 0usize; 50 | while exp > 0 { 51 | if exp & 1 == 1 { 52 | if res != 0 && calculated[res + base - 1] == 0 { 53 | cache[res + base - 1] = modq.mul_mod_fast(cache[res - 1], cache[base - 1]); 54 | calculated[res + base - 1] = 1; 55 | } 56 | res += base; 57 | } 58 | exp >>= 1; 59 | if exp != 0 { 60 | if calculated[base * 2 - 1] == 0 { 61 | cache[base * 2 - 1] = modq.mul_mod_fast(cache[base - 1], cache[base - 1]); 62 | calculated[base * 2 - 1] = 1; 63 | } 64 | base *= 2; 65 | } 66 | } 67 | } 68 | cache 69 | } 70 | 71 | fn range_fn_modulus() { 72 | let modq = Modulus::new(65537); 73 | 74 | // value on which range_fn is evaluated 75 | let v = 28192; 76 | let single = powers_of_x_modulus(v, &modq, 256); 77 | let double = powers_of_x_modulus(single[255], &modq, 255); 78 | 79 | let range_coeff = read_range_coeffs(); 80 | 81 | let mut sum = 0; 82 | for i in 0..256 { 83 | let mut tmp = 0; 84 | for j in 1..257 { 85 | tmp = modq.add_mod_fast( 86 | tmp, 87 | modq.mul_mod_fast(single[j - 1], range_coeff[(i * 256) + (j - 1)]), 88 | ); 89 | } 90 | 91 | if i == 0 { 92 | sum = tmp; 93 | } else { 94 | sum = modq.add_mod_fast(sum, modq.mul_mod_fast(double[i - 1], tmp)); 95 | } 96 | dbg!(sum); 97 | } 98 | 99 | sum = modq.sub_mod_fast(1, sum); 100 | dbg!(sum); 101 | } 102 | 103 | mod tests { 104 | use super::evaluate_powers; 105 | 106 | #[test] 107 | fn test_evaluate_even_powers() { 108 | let mut calculated = vec![0; 128]; 109 | calculated[0] = 9; 110 | calculated[4 / 2 - 1] = 3u64.pow(4); 111 | calculated[8 / 2 - 1] = 3u64.pow(8); 112 | calculated[16 / 2 - 1] = 3u64.pow(16); 113 | calculated[32 / 2 - 1] = 3u64.pow(32); 114 | calculated[64 / 2 - 1] = 3u64.pow(64); 115 | calculated[128 / 2 - 1] = 3u64.pow(128); 116 | calculated[256 / 2 - 1] = 3u64.pow(256); 117 | // even powers 118 | evaluate_powers(2, 4, &mut calculated, true); 119 | evaluate_powers(4, 8, &mut calculated, true); 120 | evaluate_powers(8, 16, &mut calculated, true); 121 | evaluate_powers(16, 32, &mut calculated, true); 122 | evaluate_powers(32, 64, &mut calculated, true); 123 | evaluate_powers(64, 128, &mut calculated, true); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /benches/range_fn.rs: -------------------------------------------------------------------------------- 1 | use bfv::{Encoding, Evaluator, Modulus, PolyType, Representation, SecretKey}; 2 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 3 | use itertools::Itertools; 4 | use ndarray::Array2; 5 | use omr::{ 6 | server::range_fn::range_fn_fma::{ 7 | mul_poly_scalar_u128, optimised_range_fn_fma_u128, scalar_mul_u128, 8 | }, 9 | utils::{generate_bfv_parameters, precompute_range_constants}, 10 | }; 11 | use rand::thread_rng; 12 | 13 | #[cfg(target_arch = "x86_64")] 14 | use omr::server::range_fn::range_fn_fma::{ 15 | fma_poly_scale_slice_hexl, mul_poly_scalar_slice_hexl, optimised_range_fn_fma_hexl, 16 | }; 17 | 18 | fn bench(c: &mut Criterion) { 19 | let mut group = c.benchmark_group("range_fn"); 20 | // group.measurement_time(Duration::new(100, 0)); 21 | // group.sample_size(10); 22 | 23 | let mut rng = thread_rng(); 24 | 25 | for degree in [1 << 12, 1 << 15] { 26 | // mod_size should give a baseline of what performance values look like per modulus iteration. More so, the time take by an operation should match its corresponding operation in hexl_rs. 27 | // For ex, for a given `degree` time taken by `fma_poly_scale_slice_hexl` when mod_size is 1 should equal time take by `hexl_rs::elwise_fma_mod` for vector size `degree`. 28 | // So far when mod_size is 1 values match their corresponidng values from hexl_rs benches. However, as mod_size increases performance time increases non-linearly. 29 | // For ex, when degree is 2^15 in `fma_poly_scale_slice_hexl` if it takes `x` us when mod size is 1, we expect it to take `15x` us when mod size is 15. However, this does 30 | // not happen. I am unsure what's the reason. Is it cache misses? Smaller cache size? 31 | for mod_size in [1, 3, 7, 15] { 32 | let params = generate_bfv_parameters(); 33 | let evaluator = Evaluator::new(params); 34 | let params = evaluator.params(); 35 | let ctx = evaluator.params().poly_ctx(&PolyType::Q, 0); 36 | 37 | let p0 = ctx.random(Representation::Evaluation, &mut rng); 38 | let p1 = ctx.random(Representation::Evaluation, &mut rng); 39 | let scalar_slice = (0..ctx.moduli_count()).map(|v| v as u64).collect_vec(); 40 | 41 | #[cfg(target_arch = "x86_64")] 42 | { 43 | let mut p0_clone = p0.clone(); 44 | group.bench_function( 45 | BenchmarkId::new( 46 | "mul_poly_scalar_slice_hexl", 47 | format!("n={degree}/mod_size={mod_size}"), 48 | ), 49 | |b| { 50 | b.iter(|| { 51 | mul_poly_scalar_slice_hexl(&ctx, &mut p0_clone, &p1, &scalar_slice); 52 | }); 53 | }, 54 | ); 55 | } 56 | 57 | #[cfg(target_arch = "x86_64")] 58 | { 59 | let mut p0_clone = p0.clone(); 60 | group.bench_function( 61 | BenchmarkId::new( 62 | "fma_poly_scale_slice_hexl", 63 | format!("n={degree}/mod_size={mod_size}"), 64 | ), 65 | |b| { 66 | b.iter(|| { 67 | fma_poly_scale_slice_hexl(&ctx, &mut p0_clone, &p1, &scalar_slice); 68 | }); 69 | }, 70 | ); 71 | } 72 | 73 | { 74 | let m = params 75 | .plaintext_modulus_op 76 | .random_vec(params.degree, &mut rng); 77 | let sk = SecretKey::random_with_params(¶ms, &mut rng); 78 | 79 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 80 | let ct = evaluator.encrypt(&sk, &pt, &mut rng); 81 | let ctx = evaluator.params().poly_ctx(&PolyType::Q, 0); 82 | let level = 0; 83 | let single_powers = vec![ct.clone(); 128]; 84 | let constants = precompute_range_constants(&ctx); 85 | 86 | #[cfg(target_arch = "x86_64")] 87 | group.bench_function( 88 | BenchmarkId::new( 89 | "optimised_range_fn_fma_hexl", 90 | format!("n={degree}/mod_size={mod_size}"), 91 | ), 92 | |b| { 93 | b.iter(|| { 94 | optimised_range_fn_fma_hexl(&ctx, &single_powers, &constants, 0, level); 95 | }); 96 | }, 97 | ); 98 | 99 | group.bench_function( 100 | BenchmarkId::new( 101 | "optimised_range_fn_fma_u128", 102 | format!("n={degree}/mod_size={mod_size}"), 103 | ), 104 | |b| { 105 | b.iter(|| { 106 | optimised_range_fn_fma_u128( 107 | &ctx, 108 | params, 109 | &single_powers, 110 | &constants, 111 | 0, 112 | level, 113 | ); 114 | }); 115 | }, 116 | ); 117 | } 118 | 119 | { 120 | let a = p0.clone(); 121 | let mut res = Array2::::zeros((ctx.moduli_count(), ctx.degree())); 122 | group.bench_function( 123 | BenchmarkId::new( 124 | "mul_poly_scalar_u128", 125 | format!("n={degree}/mod_size={mod_size}"), 126 | ), 127 | |b| { 128 | b.iter(|| mul_poly_scalar_u128(&mut res, &a, &scalar_slice)); 129 | }, 130 | ); 131 | } 132 | } 133 | 134 | { 135 | let modulus = Modulus::new(1125899904679937); 136 | let mut sum = vec![0; degree]; 137 | let a0 = modulus.random_vec(degree, &mut rng); 138 | group.bench_function( 139 | BenchmarkId::new("scalar_mul_u128", format!("n={degree}")), 140 | |b| { 141 | b.iter(|| { 142 | scalar_mul_u128(&mut sum, &a0, 1125899904679937); 143 | }); 144 | }, 145 | ); 146 | } 147 | } 148 | } 149 | 150 | criterion_group!(range_fn, bench); 151 | criterion_main!(range_fn); 152 | -------------------------------------------------------------------------------- /src/optimised.rs: -------------------------------------------------------------------------------- 1 | use crate::pvw::{PvwCiphertext, PvwParameters}; 2 | use bfv::{ 3 | mod_inverse_biguint, BfvParameters, Ciphertext, Encoding, EvaluationKey, GaloisKey, Modulus, 4 | Plaintext, Poly, PolyContext, PolyType, RelinearizationKey, Representation, SecretKey, 5 | }; 6 | use itertools::{izip, Itertools}; 7 | use ndarray::{azip, s, Array1, Array2, IntoNdProducer}; 8 | use num_bigint_dig::{BigUint, ModInverse, ToBigUint}; 9 | use num_traits::ToPrimitive; 10 | use rand::thread_rng; 11 | 12 | /// Barrett reduction of coefficients in u128 to u64 13 | pub fn barret_reduce_coefficients_u128(r_u128: &Array2, modq: &[Modulus]) -> Array2 { 14 | let v = r_u128 15 | .outer_iter() 16 | .zip(modq.iter()) 17 | .flat_map(|(r0_u128, modqi)| modqi.barret_reduction_u128_vec(r0_u128.as_slice().unwrap())) 18 | .collect_vec(); 19 | 20 | Array2::from_shape_vec((r_u128.shape()[0], r_u128.shape()[1]), v).unwrap() 21 | } 22 | 23 | pub fn coefficient_u128_to_ciphertext( 24 | params: &BfvParameters, 25 | c0_coeffs: &Array2, 26 | c1_coeffs: &Array2, 27 | level: usize, 28 | ) -> Ciphertext { 29 | let ct_ctx = params.poly_ctx(&PolyType::Q, level); 30 | 31 | Ciphertext::new( 32 | vec![ 33 | Poly::new( 34 | barret_reduce_coefficients_u128(&c0_coeffs, ct_ctx.moduli_ops()), 35 | Representation::Evaluation, 36 | ), 37 | Poly::new( 38 | barret_reduce_coefficients_u128(&c1_coeffs, ct_ctx.moduli_ops()), 39 | Representation::Evaluation, 40 | ), 41 | ], 42 | PolyType::Q, 43 | level, 44 | ) 45 | } 46 | 47 | /// We can precompute these and store them somewhere. No need to change them until we change parameters. 48 | pub fn sub_from_one_precompute(params: &BfvParameters, level: usize) -> Vec { 49 | let ctx = params.poly_ctx(&PolyType::Q, level); 50 | let q = ctx.big_q(); 51 | let q_mod_t = &q % params.plaintext_modulus; 52 | let neg_t_inv_modq = mod_inverse_biguint(&(&q - params.plaintext_modulus), &q); 53 | let res = (q_mod_t * neg_t_inv_modq) % &q; 54 | 55 | ctx.iter_moduli_ops() 56 | .map(|modqi| (&res % modqi.modulus()).to_u64().unwrap()) 57 | .collect_vec() 58 | } 59 | 60 | /// Say that you want to encode a plaintext pt in SIMD format. You must follow the following steps: 61 | /// 1. INTT(plaintext) 62 | /// 2. Matrix mapping (to enable rotations) 63 | /// 3. To add/sub resulting pt with ct, you must scale pt, ie calculate [Q/t pt]_Q. (Using remark 3.1 of 2021/204) 64 | /// To scale, we take coefficients of pt and calculate r = [Q*pt]_t and then calculate v = [r*((-t)^-1)]_Q. 65 | /// 66 | /// Notice that if pt = [1,1,..], then INTT([1,1,..]) = [1,0,0,..]. Thus our pt polynomial = [1,0,0,...]. 67 | /// Matrix mapping of index 0 is 0, causing nothing to change. To scale, we simply need to calculate [[Q]_t * -t_inv]_Q 68 | /// and set that as 0th index coefficient. Hence, scaled_pt_poly = [[[Q]_t * t_inv]_Q, 0, 0, ...]. If the ciphertext ct is in 69 | /// coefficient form, then you can simply reduce (optimisation!) calculating pt(1) - ct to `([[Q]_t * -t_inv]_Q - ct[0]) % Q`. Therefore 70 | /// instead of `degree` modulus subtraction, we do 1 + `degree - 1` subtraction. 71 | pub fn sub_from_one(params: &BfvParameters, ct: &mut Ciphertext, precomputes: &[u64]) { 72 | debug_assert!(ct.c_ref()[0].representation() == &Representation::Coefficient); 73 | 74 | let ctx = params.poly_ctx(&ct.poly_type(), ct.level()); 75 | assert!(precomputes.len() == ctx.moduli_count()); 76 | 77 | izip!( 78 | ct.c_ref_mut()[0].coefficients_mut().outer_iter_mut(), 79 | ctx.moduli_ops().iter(), 80 | precomputes.iter(), 81 | ) 82 | .for_each(|(mut coeffs, modqi, scalar)| { 83 | let qi = modqi.modulus(); 84 | // modulus subtraction for first coefficient 85 | let r = &mut coeffs[0]; 86 | if scalar > r { 87 | *r = scalar - *r; 88 | } else { 89 | *r = scalar + qi - *r; 90 | } 91 | 92 | coeffs.iter_mut().skip(1).for_each(|c| { 93 | *c = qi - *c; 94 | }) 95 | }); 96 | 97 | izip!( 98 | ct.c_ref_mut()[1].coefficients_mut().outer_iter_mut(), 99 | ctx.moduli_ops().iter(), 100 | ) 101 | .for_each(|(mut coeffs, modqi)| { 102 | let qi = modqi.modulus(); 103 | coeffs.iter_mut().for_each(|c| { 104 | *c = qi - *c; 105 | }) 106 | }); 107 | } 108 | 109 | pub fn fma_reverse_u128_vec(a: &mut [u128], b: &[u64], c: &[u64]) { 110 | izip!(a.iter_mut(), b.iter(), c.iter()).for_each(|(a0, b0, c0)| { 111 | *a0 += *b0 as u128 * *c0 as u128; 112 | }); 113 | } 114 | 115 | pub fn fma_reverse_u128_poly(d: &mut Array2, s: &Poly, h: &Poly) { 116 | debug_assert!(s.representation() == h.representation()); 117 | debug_assert!(s.representation() == &Representation::Evaluation); 118 | debug_assert!(d.shape() == s.coefficients().shape()); 119 | 120 | izip!( 121 | d.outer_iter_mut(), 122 | s.coefficients().outer_iter(), 123 | h.coefficients().outer_iter() 124 | ) 125 | .for_each(|(mut d, a, b)| { 126 | fma_reverse_u128_vec( 127 | d.as_slice_mut().unwrap(), 128 | a.as_slice().unwrap(), 129 | b.as_slice().unwrap(), 130 | ); 131 | }); 132 | } 133 | 134 | pub fn optimised_poly_fma( 135 | cts: &[Ciphertext], 136 | polys: &[Poly], 137 | res00: &mut Array2, 138 | res01: &mut Array2, 139 | ) { 140 | izip!(cts.iter(), polys.iter()).for_each(|(o, p)| { 141 | fma_reverse_u128_poly(res00, &o.c_ref()[0], p); 142 | fma_reverse_u128_poly(res01, &o.c_ref()[1], p); 143 | }); 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use bfv::{Evaluator, PolyCache}; 149 | use statrs::function::evaluate; 150 | 151 | use crate::utils::{generate_bfv_parameters, precompute_range_constants, read_range_coeffs}; 152 | 153 | use super::*; 154 | 155 | #[test] 156 | fn sub_from_one_works() { 157 | let params = generate_bfv_parameters(); 158 | let mut rng = thread_rng(); 159 | let m = params 160 | .plaintext_modulus_op 161 | .random_vec(params.degree, &mut rng); 162 | let sk = SecretKey::random_with_params(¶ms, &mut rng); 163 | 164 | let evaluator = Evaluator::new(params); 165 | 166 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 167 | let mut ct = evaluator.encrypt(&sk, &pt, &mut rng); 168 | let mut ct_clone = ct.clone(); 169 | 170 | { 171 | for _ in 0..10 { 172 | let mut ct_clone = ct.clone(); 173 | let precomputes = sub_from_one_precompute(&evaluator.params(), 0); 174 | sub_from_one(evaluator.params(), &mut ct_clone, &precomputes); 175 | } 176 | } 177 | 178 | let precomputes = sub_from_one_precompute(evaluator.params(), 0); 179 | let now = std::time::Instant::now(); 180 | sub_from_one(evaluator.params(), &mut ct, &precomputes); 181 | let time_opt = now.elapsed(); 182 | 183 | let pt = evaluator.plaintext_encode( 184 | &vec![1; evaluator.params().degree], 185 | Encoding::simd(0, PolyCache::AddSub(Representation::Coefficient)), 186 | ); 187 | let now = std::time::Instant::now(); 188 | evaluator.sub_ciphertext_from_poly_inplace(&mut ct_clone, &pt.add_sub_poly_ref()); 189 | let time_unopt = now.elapsed(); 190 | 191 | println!("Time: Opt={:?}, UnOpt={:?}", time_opt, time_unopt); 192 | println!( 193 | "Noise: Opt={:?}, UnOpt={:?}", 194 | evaluator.measure_noise(&sk, &ct), 195 | evaluator.measure_noise(&sk, &ct_clone), 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/preprocessing.rs: -------------------------------------------------------------------------------- 1 | use crate::pvw::{PvwCiphertext, PvwParameters}; 2 | use bfv::{ 3 | BfvParameters, Encoding, Evaluator, Modulus, Plaintext, Poly, PolyCache, PolyType, 4 | Representation, 5 | }; 6 | use rand::{distributions::Uniform, CryptoRng, Rng, RngCore, SeedableRng}; 7 | use rand_chacha::ChaCha8Rng; 8 | 9 | pub fn pre_process_batch( 10 | pvw_params: &PvwParameters, 11 | evaluator: &Evaluator, 12 | hints: &[PvwCiphertext], 13 | ) -> (Vec, Vec<Plaintext>) { 14 | // can only process as many as polynomial_degree hints in a batch 15 | assert!(hints.len() <= evaluator.params().degree); 16 | 17 | let sec_len = pvw_params.n.next_power_of_two(); 18 | let mut hint_a_pts = vec![]; 19 | for i in 0..sec_len { 20 | let mut m = vec![]; 21 | for j in 0..hints.len() { 22 | let index = (j + i) % sec_len; 23 | if index < pvw_params.n { 24 | m.push(hints[j].a[index]); 25 | } else { 26 | m.push(0); 27 | } 28 | } 29 | hint_a_pts 30 | .push(evaluator.plaintext_encode(&m, Encoding::simd(0, PolyCache::Mul(PolyType::Q)))); 31 | } 32 | 33 | let mut hint_b_pts = vec![]; 34 | let q_by4 = evaluator.params().plaintext_modulus / 4; 35 | for i in 0..pvw_params.ell { 36 | let mut m = vec![]; 37 | for j in 0..hints.len() { 38 | m.push( 39 | evaluator 40 | .params() 41 | .plaintext_modulus_op 42 | .sub_mod_fast(hints[j].b[i], q_by4), 43 | ); 44 | } 45 | hint_b_pts.push(evaluator.plaintext_encode( 46 | &m, 47 | Encoding::simd(0, PolyCache::AddSub(Representation::Evaluation)), 48 | )); 49 | } 50 | 51 | // length of plaintexts will be sec_len 52 | (hint_a_pts, hint_b_pts) 53 | } 54 | 55 | /// Returns plaintexts to extract blocks of extract_size. 56 | /// 57 | /// `block_size` is the size of block replicated consecutively in lanes of ciphertext. 58 | /// `extract_size` is the size of chunk that will extracted from the a block. 59 | /// For example, if block_size is 32. The ciphertext lanes look like [0,1,2,3,..31,0,1,2..31,....], 60 | /// that is block [0,1,2,3...31] is repeated across all lanes. If extract_size is 4, the function 61 | /// will return 8 (32/4=8) plaintexts to extract 1st 4 from each block, 2nd 4, 3rd 4,...8th 4. 62 | pub fn procompute_expand_roll_pt( 63 | block_size: usize, 64 | extract_size: usize, 65 | degree: usize, 66 | evaluator: &Evaluator, 67 | level: usize, 68 | ) -> Vec<Plaintext> { 69 | let parts = block_size / extract_size; 70 | let mut pts = vec![]; 71 | for part in 0..parts { 72 | let mut m = vec![]; 73 | let lower = part * extract_size; 74 | let higher = (part + 1) * extract_size; 75 | for i in 0..degree { 76 | if i % block_size >= lower && i % block_size < higher { 77 | m.push(1); 78 | } else { 79 | m.push(0); 80 | } 81 | } 82 | pts.push( 83 | evaluator.plaintext_encode(&m, Encoding::simd(level, PolyCache::Mul(PolyType::Q))), 84 | ); 85 | } 86 | pts 87 | } 88 | 89 | pub fn precompute_expand_32_roll_pt( 90 | degree: usize, 91 | evaluator: &Evaluator, 92 | level: usize, 93 | ) -> Vec<Plaintext> { 94 | assert!(degree >= 32); 95 | 96 | let mut pts = vec![]; 97 | for i in 0..(degree / 32) { 98 | let mut m = vec![0; degree]; 99 | for j in (32 * i)..(32 * (i + 1)) { 100 | m[j] = 1u64; 101 | } 102 | pts.push( 103 | evaluator.plaintext_encode(&m, Encoding::simd(level, PolyCache::Mul(PolyType::Q))), 104 | ); 105 | } 106 | 107 | pts 108 | } 109 | 110 | pub fn read_indices_poly(evaluator: &Evaluator, level: usize, min: usize, max: usize) -> Vec<Poly> { 111 | precompute_indices_pts(evaluator, level, min, max) 112 | } 113 | 114 | pub fn precompute_indices_pts( 115 | evaluator: &Evaluator, 116 | level: usize, 117 | min: usize, 118 | max: usize, 119 | ) -> Vec<Poly> { 120 | let params = evaluator.params(); 121 | let degree = params.degree; 122 | let bit_space = 64 - params.plaintext_modulus.leading_zeros() - 1; 123 | assert!(bit_space == 16); 124 | (min..std::cmp::min(degree, max)) 125 | .into_iter() 126 | .map(|i| { 127 | let mut m = vec![0u64; degree]; 128 | let col = i % 16; 129 | let row = i / 16; 130 | m[row] = 1 << col; 131 | evaluator 132 | .plaintext_encode(&m, Encoding::simd(level, PolyCache::Mul(PolyType::Q))) 133 | .move_mul_poly() 134 | }) 135 | .collect() 136 | } 137 | 138 | pub fn compute_weight_pts( 139 | evaluator: &Evaluator, 140 | level: usize, 141 | payloads: &[Vec<u64>], 142 | min: usize, 143 | max: usize, 144 | bucket_size: usize, 145 | buckets: &[Vec<u64>], 146 | weights: &[Vec<u64>], 147 | gamma: usize, 148 | ) -> Vec<Poly> { 149 | let modq = Modulus::new(65537); 150 | 151 | let params = evaluator.params(); 152 | let degree = params.degree; 153 | let bit_space = 64 - params.plaintext_modulus.leading_zeros() - 1; 154 | assert!(bit_space == 16); 155 | (min..std::cmp::min(degree, max)) 156 | .into_iter() 157 | .map(|i| { 158 | let payload = &payloads[i]; 159 | 160 | let row_buckets = &buckets[i]; 161 | let row_weights = &weights[i]; 162 | 163 | // prepare the bucket 164 | let mut m = vec![0u64; degree]; 165 | for j in 0..gamma { 166 | // get j^th bucket and corresponding weight 167 | let bucket_index = row_buckets[j]; 168 | let bucket_weight = row_weights[j]; 169 | 170 | let bucket_offset = bucket_size * (bucket_index as usize); 171 | 172 | for j in 0..bucket_size { 173 | m[j + bucket_offset] = modq.mul_mod_fast(bucket_weight, payload[j]); 174 | } 175 | } 176 | 177 | evaluator 178 | .plaintext_encode(&m, Encoding::simd(level, PolyCache::Mul(PolyType::Q))) 179 | .move_mul_poly() 180 | }) 181 | .collect() 182 | } 183 | 184 | /// Seeds a new prng and assigns `gamma` buckets and weights to 185 | /// each row in set. 186 | /// 187 | /// Note: We use the same prng for sampling buckets and weights. This 188 | /// implies that the order of sampling bucket and weight in sequence for each row 189 | /// must be followed across all clients. Moreover, both bucket and weight should 190 | /// have same type, ie u64. Care should be taken to not change bucket to 191 | /// usize otherwise values produced on runtime environments with native 192 | /// word size 32 (for example, WASM) will be different. 193 | pub fn assign_buckets_and_weights<R: CryptoRng + RngCore>( 194 | no_of_buckets: usize, 195 | gamma: usize, 196 | q_mod: u64, 197 | set_size: usize, 198 | rng: &mut R, 199 | ) -> ([u8; 32], Vec<Vec<u64>>, Vec<Vec<u64>>) { 200 | // create a seeded prng 201 | let mut seed = <ChaCha8Rng as SeedableRng>::Seed::default(); 202 | rng.fill_bytes(&mut seed); 203 | 204 | let mut buckets = Vec::with_capacity(set_size); 205 | let mut weights = Vec::with_capacity(set_size); 206 | 207 | let dist = Uniform::new(0, no_of_buckets as u64); 208 | let weight_dist = Uniform::new(1u64, q_mod); 209 | 210 | for _ in 0..set_size { 211 | let mut row_buckets = Vec::with_capacity(gamma); 212 | let mut row_weights = Vec::with_capacity(gamma); 213 | 214 | while row_buckets.len() != gamma { 215 | // random bucket 216 | let bucket = rng.sample(dist); 217 | 218 | // avoid duplicate buckets 219 | if !row_buckets.contains(&bucket) { 220 | row_buckets.push(bucket); 221 | 222 | // Assign weight 223 | // Weight cannot be zero 224 | let weight = rng.sample(weight_dist); 225 | row_weights.push(weight); 226 | } 227 | } 228 | 229 | buckets.push(row_buckets); 230 | weights.push(row_weights); 231 | } 232 | 233 | (seed, buckets, weights) 234 | } 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oblivious Message Retrieval (OMR) 2 | 3 | This repository contains an efficient implementation of [Oblivious Message Retrieval](https://eprint.iacr.org/2021/1256.pdf). 4 | 5 | ## What's OMR? 6 | 7 | Asynchronous messaging without leaking metadata is hard and every existing way to send message asynchronously requires metadata. The reason for this is obvious. To send a message to your friend asynchronously you atleast need to tag your message with your friend's identity. This enables the server to notify your friend of your message when they come online. Thus every message requires a "to" field. OMR is a way to send message asynchronously without revealing the "to" field. This means using OMR the server can deliver messages intended for your friend when they come online without ever learning which of the messages from the set of all messages were intended for them. 8 | 9 | ## Where's OMR useful? 10 | 11 | Other than being useful for asynchronous messaging without leaking metadata, OMR can be useful for improving the user experience of privacy preserving blockchains. 12 | 13 | In privacy preserving blockchains like Aztec/Zcash all transactions are encrypted. At present the only way for users to find their pertaining transactions is to download entire set (or a representation) of transactions. Then trial decrypt them to find their pertaining ones. The problem with trial decryption is obvious. Client's experience of using the chain degrades as the chain gains traction. This is because client's cost of trial decryption, in terms of bandwidth and local computation, increases linearly with no. of transactions sent on chain globally. With OMR the processing of transactions (ie messages) is offloaded to the server. The client simply comes online, requests for their encrypted digest (containing _only_ their transactions) from server, then decrypts the digest to find their transactions. With OMR client only needs to download 800Kb of data and perfom single decryption locally (decryption time: ~50ms). Hence, the entire process of retrieving transactions takes only few seconds and vastly improves user experience. 14 | 15 | ## Implementation details 16 | 17 | For security and efficiency, OMR processes messages in set of $2^{15}$. Thus it is more natural to divide OMR into two phases. The first phase, phase 1, happens on server and does not require any client interaction. The server collects all messages and divides them in batches of $2^{15}$. Then proceeds to process each batch for each client. The second phase, phase 2, starts when client requests for their encrypted message digest. Note that the client can request for messages over any arbitrary range and the restriction of batching in sets of $2^{15}$ does not holds here. 18 | 19 | ## So where are we? 20 | 21 | For users to be able to receive messages as soon as they are sent to server and for an amazing user experience of privacy preserving protocols it is important for runtime of Phase 1 and Phase 2 to be as low as possible. The good news is even though FHE computations are expensive, they are highly parallelizable. For example, as visible in benchmarks, on single thread phase 1 roughly takes 5.2 minutes and phase 2 roughly takes 14 minutes. But increasing threads to 16, reduces phase 1 time to 52.2 seconds and phase 2 time to 58 seconds. 22 | 23 | Now if you are wondering whether time reduces linearly with more cores, yes it does. Phase 2 time reduces linearly with more cores. So does Phase 1, but with a caveat of some precomputation required per user that needs to be done only once and stored. 24 | 25 | ## How to run 26 | 27 | Before running the program there are a few things to be kept in mind 28 | 29 | 1. The code is only optimised to use power of 2 no. of cores. So running it on a machine with non power of 2 cores wouldn't use all cores maximally. 30 | 2. The library has 3 features, `noise`, `level`, and `precomp_pvw`. 31 | - `noise`: Only used for debugging purposes. Not relevant for testing performance. 32 | - `level`: Enables levelled implementation. This should be enabled when testing performance. 33 | - `precomp_pvw`: Combining `level` with `precomp_pvw` will give the best performance if there are more than 8 no. of cores available. However, `precomp_pvw` comes with additonal assumptions mentioned in [PVW Precompute](#pvw-precompute). 34 | 3. The program performs best on x86 machines with AVX512IFMA instruction set. 35 | 4. OMR has high memory requirements. For example, with 16 cores phase 1 consumes around 20GB and phase 2 consumes around 100GB. 36 | <br></br> 37 | 38 | > **Note** 39 | > To compile you will require protocol buffer compiler version >= 23.4. You can install it from [here](https://grpc.io/docs/protoc-installation/#binary-install) 40 | 41 | First run setup 42 | 43 | ``` 44 | chmod +x setup.sh 45 | ./setup.sh 46 | ``` 47 | 48 | Basic structure of the command is 49 | 50 | ``` 51 | cargo run --release --features "[features]" [option] [cores] 52 | ``` 53 | 54 | 1. `features` is a string consisting of enabled features 55 | 2. `option` can either be 1,2, or 3 and must be supplied 56 | 57 | - (1) run demo 58 | - (2) prints detection key size 59 | - (3) prints the precomputed data size required per user for PVW precompute for a given no. of cores. 60 | 61 | 3. `cores` is optional. If supplied it restricts the program to use given no. of cores. Otherwise program defaults to using all available cores. 62 | 63 | To run demo with only `level` feature without PVW precompute 64 | 65 | ``` 66 | cargo run --release --features "level" 1 67 | ``` 68 | 69 | To restrict the demo to use a fixed no. of `cores` 70 | 71 | ``` 72 | cargo run --release --features "level" 1 [cores] 73 | ``` 74 | 75 | To run demo with `level` and `precomp_pvw` (best performance) 76 | 77 | ``` 78 | cargo run --release --features "level,precomp_pvw" 1 79 | ``` 80 | 81 | To print the detection key size 82 | 83 | ``` 84 | cargo run --release --features "level" 2 85 | ``` 86 | 87 | To print the precomputed data size required per user for PVW precompute for a given no. of `cores` 88 | 89 | ``` 90 | cargo run --release --features "level" 3 [cores] 91 | ``` 92 | 93 | ## PVW Precompute 94 | 95 | You can enable PVW precompute by enabling `precomp_pvw` feature. Combining `level` with `precomp_vw` feature provides best performance. However, enabling PVW precompute assumes that you are willing to store some amount of additonal precomputed data per user. This precomputation only needs to be performed once and can be stored throughout user's lifetime. 96 | 97 | If PVW precompute is not enabled then phase 1 is bottlenecked by `pvw_decrypt` part, which can only use maximum of 4 cores at once. Thus, to scale performance of phase 1 with more no. of cores PVW precompute becomes essential. 98 | 99 | You can get an estimate of data required to store per user for PVW precompute for a given number of cores by running: 100 | 101 | ``` 102 | cargo run --release --features "level" 3 [cores] 103 | ``` 104 | 105 | ## Performance 106 | 107 | All benchmarks were performed on `r6i.8xlarge` ec2 instance equipped with 3rd Gen Intel Xeon and 32vcpus (16 physical cores). 108 | 109 | Single run of phase 1 and phase 2 processes $2^{15}$ messages. 110 | <br></br> 111 | **Performance with only `level` feature:** 112 | 113 | | Cores | Phase 1 time (in ms) | Phase 2 time (in ms) | Total time (in ms) | 114 | | ------------ | -------------------- | -------------------- | ------------------ | 115 | | 1 | 313680 | 844228 | 1157908 | 116 | | 16 (32vcpus) | 52127 | 58095 | 110222 | 117 | 118 | <br></br> 119 | **Performance with `level` and `precomp_pvw` features.** 120 | (Note: cores = 1 is omitted since `precomp_pvw` only works with >=8 cores) 121 | | Cores | Phase 1 time (in ms) | Phase 2 time (in ms) | Total time (in ms) | 122 | | ------------ | -------------------- | -------------------- | ------------------ | 123 | | 16 (32vcpus) | 28202 | 57930 | 84226 124 | 125 | Storage size for PVW Precompute per user on 16 cores: 47.5 MB 126 | 127 | ## Detection Key Size 128 | 129 | Each user needs to upload detection key to server. The key does not reveal anything. It's size is **163 MB**. 130 | 131 | ## Message Digest Size 132 | 133 | The size of message digest that user downloads at the time of request is **800Kb**. 134 | 135 | ## Security 136 | 137 | Please [check](./Security.md) 138 | 139 | ## Contribution 140 | 141 | If you want to work togther on OMR (and other FHE related projects) then please check [contact](#contact) and send a dm! Also feel free to join [this](https://t.me/+rDHqU-Py34s4N2M1) telegram channel. 142 | 143 | ## Use in production 144 | 145 | If you want run OMR in production, then please check [contact](#contact) and reach out directly! 146 | 147 | ## Contact 148 | 149 | Telegram: @janmajayamall <br /> 150 | Email: janmajaya@caird.xyz 151 | 152 | ## Acknowledgements 153 | 154 | Development of OMR is supported through a grant from [Aztec](https://aztec.network/). 155 | -------------------------------------------------------------------------------- /src/server/powers_x.rs: -------------------------------------------------------------------------------- 1 | use bfv::{ 2 | BfvParameters, Ciphertext, EvaluationKey, Evaluator, GaloisKey, Plaintext, Poly, PolyType, 3 | RelinearizationKey, Representation, SecretKey, 4 | }; 5 | use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; 6 | 7 | use crate::{level_down, print_noise}; 8 | 9 | /// Calculates all powers of `x` for range [1,256) using binary exponentiation 10 | /// 11 | /// Note: not used anywhere. Here just for testing. 12 | pub fn powers_of_x_ct( 13 | x: &Ciphertext, 14 | evaluator: &Evaluator, 15 | ek: &EvaluationKey, 16 | sk: &SecretKey, 17 | ) -> Vec<Ciphertext> { 18 | let dummy = Ciphertext::new(vec![], PolyType::Q, 0); 19 | let mut values = vec![dummy; 255]; 20 | let mut calculated = vec![0u64; 255]; 21 | values[0] = x.clone(); 22 | calculated[0] = 1; 23 | // let mut mul_count = 0; 24 | 25 | for i in (2..256).rev() { 26 | let mut exp = i; 27 | let mut base_deg = 1; 28 | let mut res_deg = 0; 29 | 30 | while exp > 0 { 31 | if exp & 1 == 1 { 32 | let p_res_deg = res_deg; 33 | res_deg += base_deg; 34 | if res_deg != base_deg && calculated[res_deg - 1] == 0 { 35 | // let now = Instant::now(); 36 | let tmp = evaluator.mul(&values[p_res_deg - 1], &values[base_deg - 1]); 37 | values[res_deg - 1] = evaluator.relinearize(&tmp, ek); 38 | // println!("Res deg time: {:?}", now.elapsed()); 39 | calculated[res_deg - 1] = 1; 40 | // mul_count += 1; 41 | } 42 | } 43 | exp >>= 1; 44 | if exp != 0 { 45 | let p_base_deg = base_deg; 46 | base_deg *= 2; 47 | if calculated[base_deg - 1] == 0 { 48 | // let now = Instant::now(); 49 | let tmp = evaluator.mul(&values[p_base_deg - 1], &values[p_base_deg - 1]); 50 | values[base_deg - 1] = evaluator.relinearize(&tmp, ek); 51 | 52 | calculated[base_deg - 1] = 1; 53 | 54 | // mul_count += 1; 55 | } 56 | } 57 | } 58 | } 59 | // dbg!(mul_count); 60 | 61 | values 62 | } 63 | 64 | /// Calcultes power of x for range [start, end) in parallel. Assumes that all 65 | /// powers in range [0, start) are pre-calculated and stored in `calculated`. 66 | /// Assumes that `start` and `end=start*2` are power of 2. 67 | /// 68 | /// Set `bases` to true if `base` power for the range has already calculated and 69 | /// stored in `calculated`. 70 | /// 71 | /// If base is not calculated, then function performs end-start ciphertext multiplications. 72 | /// Otherwise performs end-start-1 ciphertext multiplications. 73 | /// 74 | /// To understand the function, consider the base power as `x^start`. We assume that 75 | /// `start` is always a power of 2. All values in range [start, end) can be calculated 76 | /// by adding base with some value in range [0,start). For example, if start is 128 77 | /// (b10000000) then to calculate 250 (b 1 1111010), we write it as sum of 128 + 78 | /// 122 (b 0 1111010). Thus, to calculate x^250 by multiply `x^128 * x^122`. 79 | pub fn evaluate_powers( 80 | evaluator: &Evaluator, 81 | ek: &EvaluationKey, 82 | start: usize, 83 | end: usize, 84 | calculated: &mut [Ciphertext], 85 | bases: bool, 86 | sk: &SecretKey, 87 | ) { 88 | // start is always a power of two. Hence, start-1 equals the mask to extract log2(start) bits 89 | let mask = start - 1; 90 | 91 | // To calculate values in range (start, start*2) we require value for start, that is the base 92 | if !bases { 93 | let tmp = evaluator.mul(&calculated[start / 2 - 1], &calculated[start / 2 - 1]); 94 | calculated[start - 1] = evaluator.relinearize(&tmp, ek); 95 | 96 | level_down!(if start == 2 || start == 8 || start == 32 || start == 64 { 97 | evaluator.mod_down_next(&mut calculated[start - 1]); 98 | }); 99 | 100 | print_noise!(println!( 101 | " base {start} noise: {}", 102 | evaluator.measure_noise(&sk, &calculated[start - 1]) 103 | );); 104 | } 105 | 106 | // To avoid running into borrow issues later, split `calculated` at `start+1` where the second chunk will 107 | // be mutated 108 | let (done, pending) = calculated.split_at_mut(start - 1 + 1); 109 | 110 | // Since we only need to calculate for range [start+1, end-1], slice off the rest 111 | let pending = &mut pending[..(end - 1 - start)]; 112 | 113 | level_down!( 114 | // mod match 115 | let match_level = done.last().unwrap().level(); 116 | done.par_iter_mut().for_each(|ct| { 117 | evaluator.mod_down_level(ct, match_level); 118 | }); 119 | ); 120 | 121 | pending.par_iter_mut().enumerate().for_each(|(index, v)| { 122 | // calculate real_index for power to figure out which other power to multiply base with. 123 | // For ex, if real_index = 5 (ie x^5) then we must multiply x^4 with x^1 since x^4 is the base 124 | // and 1 equals to `5 & mask` (ie 101 & 011 = 1). 125 | let real_index = index + start + 1; 126 | 127 | let tmp = evaluator.mul(done.last().unwrap(), &done[(real_index & mask) - 1]); 128 | *v = evaluator.relinearize(&tmp, ek); 129 | }); 130 | 131 | // let pending_chunks = pending.par_chunks_mut(size); 132 | // pending_chunks 133 | // .enumerate() 134 | // .into_par_iter() 135 | // .for_each(|(index, chunk)| { 136 | // chunk.iter_mut().enumerate().for_each(|(chunk_index, v)| { 137 | // // calculate real_index of the value to figure out the mask 138 | // let real_index = size * index + chunk_index + start + 1; 139 | // let tmp = evaluator.mul(done.last().unwrap(), &done[(real_index & mask) - 1]); 140 | // *v = evaluator.relinearize(&tmp, ek); 141 | // }); 142 | // }); 143 | } 144 | 145 | mod tests { 146 | use crate::{plaintext::powers_of_x_modulus, utils::generate_bfv_parameters}; 147 | 148 | use super::*; 149 | use bfv::{BfvParameters, Encoding}; 150 | use itertools::izip; 151 | use rand::thread_rng; 152 | 153 | #[test] 154 | fn test_evaluate_powers() { 155 | let mut rng = thread_rng(); 156 | let params = generate_bfv_parameters(); 157 | let sk = SecretKey::random_with_params(&params, &mut rng); 158 | let m = vec![3; params.degree]; 159 | 160 | let evaluator = Evaluator::new(params); 161 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 162 | let ct = evaluator.encrypt(&sk, &pt, &mut rng); 163 | let ek = EvaluationKey::new(evaluator.params(), &sk, &[0], &[], &[], &mut rng); 164 | let dummy = Ciphertext::new(vec![], PolyType::Q, 0); 165 | 166 | let cores = 4; 167 | 168 | // warm up 169 | { 170 | let mut calculated = vec![dummy.clone(); 255]; 171 | calculated[0] = ct.clone(); 172 | for _ in 0..10 { 173 | evaluate_powers(&evaluator, &ek, 2, 4, &mut calculated, false, &sk); 174 | } 175 | } 176 | 177 | let now = std::time::Instant::now(); 178 | let mut calculated = vec![dummy.clone(); 255]; 179 | calculated[0] = ct; 180 | 181 | let pool = rayon::ThreadPoolBuilder::new() 182 | .num_threads(4) 183 | .build() 184 | .unwrap(); 185 | 186 | pool.install(|| { 187 | evaluate_powers(&evaluator, &ek, 2, 4, &mut calculated, false, &sk); 188 | evaluate_powers(&evaluator, &ek, 4, 8, &mut calculated, false, &sk); 189 | evaluate_powers(&evaluator, &ek, 8, 16, &mut calculated, false, &sk); 190 | evaluate_powers(&evaluator, &ek, 16, 32, &mut calculated, false, &sk); 191 | evaluate_powers(&evaluator, &ek, 32, 64, &mut calculated, false, &sk); 192 | evaluate_powers(&evaluator, &ek, 64, 128, &mut calculated, false, &sk); 193 | evaluate_powers(&evaluator, &ek, 128, 256, &mut calculated, false, &sk); 194 | }); 195 | println!("Time: {:?}", now.elapsed()); 196 | 197 | let res_values_mod = powers_of_x_modulus(3, &evaluator.params().plaintext_modulus_op, 255); 198 | izip!(calculated.iter(), res_values_mod.iter()).for_each(|(pct, v)| { 199 | // dbg!(evaluator.measure_noise(&sk, pct)); 200 | let r = evaluator.plaintext_decode(&evaluator.decrypt(&sk, pct), Encoding::default()); 201 | r.iter().for_each(|r0| { 202 | assert!(r0 == v); 203 | }); 204 | }); 205 | } 206 | 207 | #[test] 208 | fn powers_of_x_ct_works() { 209 | let mut rng = thread_rng(); 210 | let params = generate_bfv_parameters(); 211 | let sk = SecretKey::random_with_params(&params, &mut rng); 212 | let m = vec![3; params.degree]; 213 | 214 | let evaluator = Evaluator::new(params); 215 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 216 | let ct = evaluator.encrypt(&sk, &pt, &mut rng); 217 | let ek = EvaluationKey::new(evaluator.params(), &sk, &[0], &[], &[], &mut rng); 218 | 219 | // { 220 | // for _ in 0..1 { 221 | // powers_of_x_ct(&ct, &evaluator, &ek, &sk); 222 | // } 223 | // } 224 | 225 | let now = std::time::Instant::now(); 226 | let powers_ct = powers_of_x_ct(&ct, &evaluator, &ek, &sk); 227 | println!("Time = {:?}", now.elapsed()); 228 | 229 | let res_values_mod = powers_of_x_modulus(3, &evaluator.params().plaintext_modulus_op, 255); 230 | 231 | izip!(powers_ct.iter(), res_values_mod.iter()).for_each(|(pct, v)| { 232 | dbg!(evaluator.measure_noise(&sk, pct)); 233 | let r = evaluator.plaintext_decode(&evaluator.decrypt(&sk, pct), Encoding::default()); 234 | r.iter().for_each(|r0| { 235 | assert!(r0 == v); 236 | }); 237 | }); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/pvw/pvw.rs: -------------------------------------------------------------------------------- 1 | use super::PvwCiphertextProto; 2 | use bfv::{convert_from_bytes, convert_to_bytes, Modulus}; 3 | use itertools::{izip, Itertools}; 4 | use ndarray::{Array, Array1, Array2, Axis}; 5 | use rand::{ 6 | distributions::{Distribution, Uniform}, 7 | thread_rng, CryptoRng, RngCore, SeedableRng, 8 | }; 9 | use rand_chacha::ChaChaRng; 10 | use statrs::distribution::Normal; 11 | use traits::TryFromWithParameters; 12 | 13 | #[derive(Clone, Debug, PartialEq)] 14 | pub struct PvwParameters { 15 | pub n: usize, 16 | pub m: usize, 17 | pub ell: usize, 18 | pub variance: f64, 19 | pub q: u64, 20 | } 21 | 22 | impl Default for PvwParameters { 23 | fn default() -> Self { 24 | Self { 25 | n: 450, 26 | m: 16000, 27 | ell: 4, 28 | variance: 1.3, 29 | q: 65537, 30 | } 31 | } 32 | } 33 | 34 | #[derive(Clone, Debug, PartialEq)] 35 | pub struct PvwCiphertext { 36 | par: PvwParameters, 37 | pub a: Vec<u64>, 38 | pub b: Vec<u64>, 39 | } 40 | 41 | pub struct PvwPublicKey { 42 | a: Array2<u64>, 43 | b: Array2<u64>, 44 | seed: <ChaChaRng as SeedableRng>::Seed, 45 | par: PvwParameters, 46 | } 47 | 48 | impl PvwPublicKey { 49 | pub fn encrypt<R: RngCore + CryptoRng>(&self, m: &[u64], rng: &mut R) -> PvwCiphertext { 50 | debug_assert!(m.len() == self.par.ell); 51 | 52 | let error = Uniform::new(0u64, 2) 53 | .sample_iter(rng) 54 | .take(self.par.m) 55 | .collect_vec(); 56 | 57 | let q = Modulus::new(self.par.q); 58 | let ae = Array1::from_vec( 59 | self.a 60 | .outer_iter() 61 | .map(|a_n_m| { 62 | let mut sum = 0; 63 | izip!(a_n_m.iter(), error.iter()).for_each(|(a, e)| { 64 | sum += a * e; 65 | }); 66 | sum = q.reduce(sum); 67 | sum 68 | }) 69 | .collect(), 70 | ); 71 | 72 | let t = m.iter().map(|v| { 73 | if *v == 1 { 74 | (3 * self.par.q) / 4 75 | } else { 76 | self.par.q / 4 77 | } 78 | }); 79 | let be = Array1::from_vec( 80 | izip!(self.b.outer_iter(), t) 81 | .map(|(b_ell_m, ti)| { 82 | let mut sum = 0; 83 | izip!(b_ell_m.iter(), error.iter()).for_each(|(a, e)| { 84 | sum += a * e; 85 | }); 86 | sum += ti; 87 | sum = q.reduce(sum); 88 | sum 89 | }) 90 | .collect(), 91 | ); 92 | 93 | PvwCiphertext { 94 | par: self.par.clone(), 95 | a: ae.to_vec(), 96 | b: be.to_vec(), 97 | } 98 | } 99 | } 100 | 101 | pub struct PvwSecretKey { 102 | pub key: Array2<u64>, 103 | pub par: PvwParameters, 104 | } 105 | 106 | impl PvwSecretKey { 107 | pub fn random<R: RngCore + CryptoRng>(params: &PvwParameters, rng: &mut R) -> PvwSecretKey { 108 | let q = Modulus::new(params.q); 109 | 110 | let sk = Array::from_shape_vec( 111 | (params.ell, params.n), 112 | q.random_vec(params.n * params.ell, rng), 113 | ) 114 | .unwrap(); 115 | 116 | PvwSecretKey { 117 | key: sk, 118 | par: params.clone(), 119 | } 120 | } 121 | 122 | pub fn public_key<R: RngCore + CryptoRng>(&self, rng: &mut R) -> PvwPublicKey { 123 | let q = Modulus::new(self.par.q); 124 | 125 | let mut seed = <ChaChaRng as SeedableRng>::Seed::default(); 126 | thread_rng().fill_bytes(&mut seed); 127 | let mut seeded_prng = ChaChaRng::from_seed(seed); 128 | 129 | let a = Array::from_shape_vec( 130 | (self.par.n, self.par.m), 131 | q.random_vec(self.par.n * self.par.m, &mut seeded_prng), 132 | ) 133 | .unwrap(); 134 | 135 | // sk * a; 136 | let distr = Normal::new(0.0, self.par.variance).unwrap(); 137 | let error = Array::from_shape_vec( 138 | (self.par.ell, self.par.m), 139 | q.reduce_vec_i64_small( 140 | &distr 141 | .sample_iter(rng) 142 | .take(self.par.ell * self.par.m) 143 | .map(|v| v.round() as i64) 144 | .collect_vec(), 145 | ), 146 | ) 147 | .unwrap(); 148 | 149 | let mut ska = izip!(self.key.outer_iter(), error.outer_iter()) 150 | .flat_map(|(key_ell_n, e_ell_m)| { 151 | let key_ell_n = key_ell_n.as_slice().unwrap(); 152 | let ska_ell_m = izip!(a.axis_iter(Axis(1)), e_ell_m.iter()) 153 | .map(|(m_n, e_value)| { 154 | let mut r = m_n.to_vec(); 155 | q.mul_mod_fast_vec(&mut r, key_ell_n); 156 | let r = (r.iter().sum::<u64>()) + *e_value; 157 | r 158 | }) 159 | .collect_vec(); 160 | ska_ell_m 161 | }) 162 | .collect_vec(); 163 | q.reduce_vec(&mut ska); 164 | let ska = Array::from_shape_vec((self.par.ell, self.par.m), ska).unwrap(); 165 | 166 | PvwPublicKey { 167 | a, 168 | b: ska, 169 | par: self.par.clone(), 170 | seed, 171 | } 172 | } 173 | 174 | pub fn decrypt(&self, ct: PvwCiphertext) -> Vec<u64> { 175 | let q = Modulus::new(self.par.q); 176 | 177 | izip!(ct.b.iter(), self.key.outer_iter()) 178 | .map(|(b_ell, k_ell_n)| { 179 | let mut r = ct.a.clone(); 180 | q.mul_mod_fast_vec(&mut r, &k_ell_n.to_vec()); 181 | let d = q.sub_mod_fast(*b_ell, q.reduce(r.iter().sum::<u64>())); 182 | (d >= self.par.q / 2) as u64 183 | }) 184 | .collect() 185 | } 186 | 187 | pub fn decrypt_shifted(&self, ct: PvwCiphertext) -> Vec<u64> { 188 | let q = Modulus::new(self.par.q); 189 | 190 | izip!(ct.b.iter(), self.key.outer_iter()) 191 | .map(|(b_ell, k_ell_n)| { 192 | let mut r = ct.a.clone(); 193 | q.mul_mod_fast_vec(&mut r, &k_ell_n.to_vec()); 194 | 195 | // shift value left by q/4 so that 196 | // indices encrypting 0 are near value 0. 197 | let d = q.sub_mod_fast( 198 | q.sub_mod_fast(*b_ell, q.reduce(r.iter().sum::<u64>())), 199 | self.par.q / 4, 200 | ); 201 | 202 | // Now values encrypting zero should be in range 203 | // q - 850 < v < 850 with high probability 204 | !(self.par.q - 850 <= d || d <= 850) as u64 205 | }) 206 | .collect() 207 | } 208 | } 209 | 210 | impl TryFromWithParameters for PvwCiphertextProto { 211 | type Parameters = PvwParameters; 212 | type Value = PvwCiphertext; 213 | fn try_from_with_parameters(value: &Self::Value, parameters: &Self::Parameters) -> Self { 214 | let a_bytes = convert_to_bytes(&value.a, parameters.q); 215 | let b_bytes = convert_to_bytes(&value.b, parameters.q); 216 | 217 | PvwCiphertextProto { 218 | b: b_bytes, 219 | a: a_bytes, 220 | } 221 | } 222 | } 223 | 224 | impl TryFromWithParameters for PvwCiphertext { 225 | type Parameters = PvwParameters; 226 | type Value = PvwCiphertextProto; 227 | fn try_from_with_parameters(value: &Self::Value, parameters: &Self::Parameters) -> Self { 228 | let a_bytes = convert_from_bytes(&value.a, parameters.q); 229 | let b_bytes = convert_from_bytes(&value.b, parameters.q); 230 | 231 | PvwCiphertext { 232 | par: parameters.clone(), 233 | a: a_bytes, 234 | b: b_bytes, 235 | } 236 | } 237 | } 238 | 239 | #[cfg(test)] 240 | mod tests { 241 | 242 | use super::*; 243 | use rand::thread_rng; 244 | 245 | #[test] 246 | fn encrypt() { 247 | let mut rng = thread_rng(); 248 | let params = PvwParameters::default(); 249 | for _ in 0..2 { 250 | let sk = PvwSecretKey::random(&params, &mut rng); 251 | let pk = sk.public_key(&mut rng); 252 | 253 | let distr = Uniform::new(0u64, 2); 254 | let m = distr 255 | .sample_iter(rng.clone()) 256 | .take(params.ell) 257 | .collect_vec(); 258 | let ct = pk.encrypt(&m, &mut rng); 259 | dbg!(ct.a.len(), ct.b.len()); 260 | 261 | let d_m = sk.decrypt(ct); 262 | 263 | assert_eq!(m, d_m) 264 | } 265 | } 266 | 267 | #[test] 268 | fn check_probs() { 269 | let params = PvwParameters::default(); 270 | 271 | let mut rng = thread_rng(); 272 | let sk = PvwSecretKey::random(&params, &mut rng); 273 | let pk = sk.public_key(&mut rng); 274 | 275 | let sk1 = PvwSecretKey::random(&params, &mut rng); 276 | let pk1 = sk1.public_key(&mut rng); 277 | 278 | let mut count = 0; 279 | let mut count1 = 0; 280 | let observations = 10000; 281 | for _ in 0..observations { 282 | let ct = pk.encrypt(&[0, 0, 0, 0], &mut rng); 283 | let ct1 = pk1.encrypt(&[0, 0, 0, 0], &mut rng); 284 | 285 | if sk.decrypt_shifted(ct) == vec![0, 0, 0, 0] { 286 | count += 1; 287 | } 288 | 289 | if sk.decrypt_shifted(ct1) == vec![0, 0, 0, 0] { 290 | count1 += 1; 291 | } 292 | } 293 | assert!((count as f64 / observations as f64) == 1.0); 294 | assert!((count1 as f64 / observations as f64) == 0.0); 295 | } 296 | 297 | #[test] 298 | fn serialize_and_deserialize_pvw_ciphertext() { 299 | let params = PvwParameters::default(); 300 | 301 | let mut rng = thread_rng(); 302 | let sk = PvwSecretKey::random(&params, &mut rng); 303 | let pk = sk.public_key(&mut rng); 304 | let ct = pk.encrypt(&[0, 0, 0, 0], &mut rng); 305 | 306 | let proto = PvwCiphertextProto::try_from_with_parameters(&ct, &params); 307 | let ct_back = PvwCiphertext::try_from_with_parameters(&proto, &params); 308 | 309 | assert_eq!(ct, ct_back); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use bfv::{ 2 | BfvParameters, Ciphertext, Encoding, Evaluator, Modulus, PolyContext, RelinearizationKey, 3 | SecretKey, 4 | }; 5 | use byteorder::{ByteOrder, LittleEndian}; 6 | use bytes::Bytes; 7 | use itertools::Itertools; 8 | use ndarray::Array2; 9 | use prost::Message; 10 | use rand::{ 11 | distributions::{Standard, Uniform}, 12 | thread_rng, Rng, 13 | }; 14 | use rayon::{ 15 | prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelBridge, ParallelIterator}, 16 | vec, 17 | }; 18 | use std::{ 19 | io::{BufReader, Read, Write}, 20 | path::{Path, PathBuf}, 21 | }; 22 | use traits::TryFromWithParameters; 23 | use walkdir::{DirEntry, WalkDir}; 24 | 25 | use crate::{ 26 | pvw::{PvwCiphertext, PvwCiphertextProto, PvwParameters, PvwPublicKey, PvwSecretKey}, 27 | MESSAGE_BYTES, 28 | }; 29 | 30 | // Measures time in ms for enclosed code block. 31 | // Credit: https://github.com/zama-ai/demo_z8z/blob/1f24eeaf006263543062e90f1d1692d381a726cf/src/zqz/utils.rs#L28C1-L42C2 32 | #[macro_export] 33 | macro_rules! time_it{ 34 | ($title: tt, $($block:tt)+) => { 35 | let __now = std::time::SystemTime::now(); 36 | $( 37 | $block 38 | )+ 39 | let __time = __now.elapsed().unwrap().as_millis(); 40 | let __ms_time = format!("{} ms", __time); 41 | println!("{} duration: {}", $title, __ms_time); 42 | } 43 | } 44 | 45 | #[macro_export] 46 | macro_rules! print_noise { 47 | ($($block:tt)+) => { 48 | #[cfg(feature="noise")] 49 | { 50 | $( 51 | $block 52 | )+ 53 | } 54 | }; 55 | } 56 | 57 | #[macro_export] 58 | macro_rules! level_down { 59 | ($($block:tt)+) => { 60 | #[cfg(feature="level")] 61 | { 62 | $( 63 | $block 64 | )+ 65 | } 66 | }; 67 | } 68 | 69 | pub fn read_range_coeffs() -> Vec<u64> { 70 | let bytes = std::fs::read("./data/params_850.bin").expect("./data/params_850.bin not found"); 71 | let mut coeffs = [0u64; 65536]; 72 | LittleEndian::read_u64_into(&bytes, &mut coeffs); 73 | coeffs.to_vec() 74 | } 75 | 76 | pub fn store_range_coeffs() { 77 | let prime = 65537; 78 | let range = 850; 79 | let mut sums = vec![]; 80 | for i in 1..prime { 81 | let mut sum = 0; 82 | let modq = Modulus::new(prime); 83 | for a in 0..prime { 84 | if a <= range || a >= (prime - range) { 85 | sum = modq.add_mod(sum, modq.exp(a, (prime - 1 - i).try_into().unwrap())); 86 | } 87 | } 88 | sums.push(sum); 89 | } 90 | let mut buf = [0u8; 65536 * 8]; 91 | LittleEndian::write_u64_into(&sums, &mut buf); 92 | 93 | let output_dir = Path::new("./data"); 94 | std::fs::create_dir_all(output_dir).expect("Create ./data failed"); 95 | let mut file_path = PathBuf::from(output_dir); 96 | file_path.push("params_850.bin"); 97 | let mut f = std::fs::File::create(file_path).unwrap(); 98 | f.write_all(&buf).unwrap(); 99 | } 100 | 101 | pub fn precompute_range_constants(ctx: &PolyContext<'_>) -> Array2<u64> { 102 | let coeffs = read_range_coeffs(); 103 | let v = coeffs 104 | .iter() 105 | .flat_map(|c| ctx.iter_moduli_ops().map(|modqi| *c % modqi.modulus())) 106 | .collect_vec(); 107 | 108 | Array2::from_shape_vec((65536usize, ctx.moduli_count()), v).unwrap() 109 | } 110 | 111 | pub fn generate_random_payloads(set_size: usize) -> Vec<Vec<u64>> { 112 | let rng = thread_rng(); 113 | let mut payloads = Vec::with_capacity(set_size); 114 | (0..set_size).into_iter().for_each(|_| { 115 | let msg: Vec<u64> = rng 116 | .clone() 117 | .sample_iter(Uniform::new(0, (1 << 16))) 118 | .take(MESSAGE_BYTES / 2) 119 | .collect_vec(); 120 | payloads.push(msg); 121 | }); 122 | payloads 123 | } 124 | 125 | pub fn generate_bfv_parameters() -> BfvParameters { 126 | let moduli = vec![50, 50, 50, 60, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50]; 127 | let mut params = BfvParameters::new(&moduli, 65537, 1 << 15); 128 | params.enable_hybrid_key_switching(&[50, 50, 60]); 129 | params 130 | } 131 | 132 | /// Generates random clues using a single public key. 133 | fn generate_random_clues(pvw_params: &PvwParameters, set_size: usize) -> Vec<PvwCiphertext> { 134 | let mut rng = thread_rng(); 135 | let random_sk = PvwSecretKey::random(pvw_params, &mut rng); 136 | let pk = random_sk.public_key(&mut rng); 137 | 138 | let mut clues = vec![]; 139 | (0..set_size) 140 | .into_par_iter() 141 | .map(|_| { 142 | // TODO: figure out a better way 143 | let mut rng = thread_rng(); 144 | pk.encrypt(&[0, 0, 0, 0], &mut rng) 145 | }) 146 | .collect_into_vec(&mut clues); 147 | 148 | clues 149 | } 150 | 151 | fn store_clues(clues: &[PvwCiphertext], params: &PvwParameters) { 152 | let output_dir = Path::new("./data/clues"); 153 | std::fs::create_dir_all("./data/clues").expect("Create clue directory failed"); 154 | 155 | clues.iter().enumerate().for_each(|(index, c)| { 156 | let bytes = PvwCiphertextProto::try_from_with_parameters(c, params).encode_to_vec(); 157 | let mut clue_path = PathBuf::from(output_dir); 158 | clue_path.push(format!("{index}.bin")); 159 | 160 | let mut f = std::fs::File::create(clue_path).expect("Couldn't create clue file"); 161 | f.write_all(&bytes) 162 | .expect("Couldn't write bytes to clue file"); 163 | }); 164 | } 165 | 166 | fn read_clues(params: &PvwParameters) -> Vec<PvwCiphertext> { 167 | let paths = std::fs::read_dir("./data/clues").expect("data/clue directory not found"); 168 | let mut cts = vec![]; 169 | paths.into_iter().for_each(|path| { 170 | let path = path.unwrap(); 171 | 172 | if path.file_type().unwrap().is_file() { 173 | let file = std::fs::read(path.path()) 174 | .expect(&format!("Unable to open file at {:?}", path.path())); 175 | let bytes = Bytes::from(file); 176 | let proto = PvwCiphertextProto::decode(bytes) 177 | .expect(&format!("Invalid clue file {:?}", path.path())); 178 | let ct = PvwCiphertext::try_from_with_parameters(&proto, params); 179 | cts.push(ct); 180 | } 181 | }); 182 | cts 183 | } 184 | 185 | fn is_bin(entry: &Result<walkdir::DirEntry, walkdir::Error>) -> bool { 186 | entry.as_ref().map_or(false, |c| { 187 | c.file_name() 188 | .to_str() 189 | .map(|s| s.contains(".bin")) 190 | .unwrap_or(false) 191 | }) 192 | } 193 | 194 | /// If random clues have been generated and stored under ./data/clues then the function 195 | /// reads them, otherwise generates new random clues and store them under ./data/clues 196 | /// taking significantly longer time. Once random clues have either been read or generated 197 | /// the function generates new pertinent clues for `pk` and places them at specific indices 198 | /// in `clues` vector according to `pertinency_indices`. 199 | pub fn prepare_clues_for_demo( 200 | pvw_params: &PvwParameters, 201 | pk: &PvwPublicKey, 202 | pertinent_indices: &[usize], 203 | set_size: usize, 204 | ) -> Vec<PvwCiphertext> { 205 | let clue_dir = Path::new("./data/clues"); 206 | // std::fs::read_dir(clue_dir).expect(&format!("Cannot open {}", clue_dir.to_str().unwrap())).si; 207 | let mut clues = vec![]; 208 | 209 | if WalkDir::new(clue_dir).into_iter().filter(is_bin).count() < set_size { 210 | println!("/data/clues not found. Generating random clues..."); 211 | time_it!("Generate random clues", 212 | clues = generate_random_clues(pvw_params, set_size);); 213 | // store clues for later 214 | store_clues(&clues, pvw_params); 215 | } else { 216 | println!("Reading clues stored under /data/clues..."); 217 | clues = read_clues(pvw_params); 218 | clues.truncate(set_size); 219 | } 220 | 221 | // generate pertinent clues and place them at pertinent indices 222 | println!("Generating pertinent clues..."); 223 | let mut rng = thread_rng(); 224 | pertinent_indices.iter().for_each(|i| { 225 | clues[*i] = pk.encrypt(&[0, 0, 0, 0], &mut rng); 226 | }); 227 | 228 | clues 229 | } 230 | 231 | /// All non pertinent clues are clone of a single clues generated under a random public key. 232 | /// All pertinent clues are generated fresh using `pvw_pk`. Only used for testing purposes. 233 | fn generate_clues_fast( 234 | pvw_pk: &PvwPublicKey, 235 | pvw_params: &PvwParameters, 236 | pertinent_indices: &[usize], 237 | count: usize, 238 | ) -> Vec<PvwCiphertext> { 239 | let mut rng = thread_rng(); 240 | 241 | let other_pvw_sk = PvwSecretKey::random(pvw_params, &mut rng); 242 | let other_pvw_pk = other_pvw_sk.public_key(&mut rng); 243 | let non_peritnent_clue = other_pvw_pk.encrypt(&[0, 0, 0, 0], &mut rng); 244 | 245 | // generate hints 246 | let partinent_clue = pvw_pk.encrypt(&[0, 0, 0, 0], &mut rng); 247 | let clues = (0..count) 248 | .into_iter() 249 | .map(|i| { 250 | if pertinent_indices.contains(&i) { 251 | partinent_clue.clone() 252 | } else { 253 | non_peritnent_clue.clone() 254 | } 255 | }) 256 | .collect_vec(); 257 | 258 | clues 259 | } 260 | 261 | mod tests { 262 | use std::collections::HashSet; 263 | 264 | use bfv::Modulus; 265 | 266 | use super::*; 267 | 268 | #[test] 269 | fn test_store_range_coeffs() { 270 | store_range_coeffs(); 271 | } 272 | 273 | #[test] 274 | fn generate_and_store_random_clues() { 275 | let pvw_params = PvwParameters::default(); 276 | let mut clues = generate_random_clues(&pvw_params, 1 << 15); 277 | store_clues(&clues, &pvw_params); 278 | let mut clues_back = read_clues(&pvw_params); 279 | } 280 | 281 | #[test] 282 | fn range_coeffs_zeros_count() { 283 | let coeffs = read_range_coeffs(); 284 | let mut count0 = 0; 285 | let mut count1 = 0; 286 | coeffs.iter().for_each(|c| { 287 | if *c == 0 { 288 | count0 += 1; 289 | } 290 | if *c == 2 { 291 | count1 += 1; 292 | } 293 | }); 294 | coeffs.iter().step_by(2).for_each(|c| assert!(*c == 0)); 295 | dbg!(count0); 296 | dbg!(count1); 297 | dbg!(coeffs.iter().max()); 298 | // println!("{:?}", &coeffs[..2]); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::pvw::{PvwCiphertext, PvwParameters, PvwSecretKey}; 2 | use bfv::{ 3 | rot_to_galois_element, BfvParameters, Ciphertext, Encoding, EvaluationKey, Evaluator, 4 | GaloisKey, Modulus, Plaintext, Representation, SecretKey, 5 | }; 6 | use core::panic; 7 | use itertools::Itertools; 8 | use rand::{thread_rng, CryptoRng, RngCore}; 9 | use rayon::vec; 10 | 11 | /// Encrypts pvw sk under bfv in desired form 12 | pub fn encrypt_pvw_sk<R: CryptoRng + RngCore>( 13 | evaluator: &Evaluator, 14 | bfv_sk: &SecretKey, 15 | pvw_sk: &PvwSecretKey, 16 | rng: &mut R, 17 | ) -> Vec<Ciphertext> { 18 | let sec_len = pvw_sk.par.n.next_power_of_two(); 19 | let degree = evaluator.params().degree; 20 | 21 | // pvw_sk.key is of dimension ell x n 22 | let cts = pvw_sk 23 | .key 24 | .outer_iter() 25 | .map(|s| { 26 | let mut m = vec![]; 27 | for i in 0..degree { 28 | let index = i % sec_len; 29 | if index < pvw_sk.par.n { 30 | m.push(s[index]); 31 | } else { 32 | m.push(0); 33 | } 34 | } 35 | 36 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 37 | let mut ct = evaluator.encrypt(bfv_sk, &pt, rng); 38 | evaluator.ciphertext_change_representation(&mut ct, Representation::Evaluation); 39 | ct 40 | }) 41 | .collect_vec(); 42 | 43 | cts 44 | } 45 | 46 | pub fn evaluation_key<R: CryptoRng + RngCore>( 47 | params: &BfvParameters, 48 | sk: &SecretKey, 49 | rng: &mut R, 50 | ) -> EvaluationKey { 51 | #[cfg(feature = "level")] 52 | let rlk_levels = (0..12).into_iter().collect_vec(); 53 | 54 | #[cfg(not(feature = "level"))] 55 | let rlk_levels = vec![0]; 56 | 57 | let level = 12; 58 | let (mut rtg_indices, mut rtg_levels) = get_pv_expand_rtgs_vecs(level, params.degree); 59 | 60 | // pvw rot key 61 | rtg_indices.push(1); 62 | rtg_levels.push(0); 63 | 64 | EvaluationKey::new(params, sk, &rlk_levels, &rtg_levels, &rtg_indices, rng) 65 | } 66 | 67 | pub fn gen_pv_exapnd_rtgs<R: CryptoRng + RngCore>( 68 | params: &BfvParameters, 69 | sk: &SecretKey, 70 | level: usize, 71 | rng: &mut R, 72 | ) -> EvaluationKey { 73 | // create galois keys 74 | let (rtg_indices, rtg_levels) = get_pv_expand_rtgs_vecs(level, params.degree); 75 | EvaluationKey::new(params, sk, &[], &rtg_levels, &rtg_indices, rng) 76 | } 77 | 78 | pub fn get_pv_expand_rtgs_vecs(level: usize, degree: usize) -> (Vec<isize>, Vec<usize>) { 79 | let mut rtg_indices = vec![]; 80 | let mut rtg_levels = vec![]; 81 | // keys for 32 expand 82 | let mut i = 32; 83 | while i < degree as isize { 84 | rtg_indices.push(i); 85 | rtg_levels.push(level); 86 | i *= 2; 87 | } 88 | // row swap 89 | rtg_indices.push(2 * degree as isize - 1); 90 | rtg_levels.push(level); 91 | 92 | // keys for 4 expand 93 | let mut i = 4; 94 | while i < 32 { 95 | rtg_indices.push(i); 96 | rtg_levels.push(level); 97 | i *= 2; 98 | } 99 | 100 | // keys for 1 expand 101 | let mut i = 1; 102 | while i < 4 { 103 | rtg_indices.push(i); 104 | rtg_levels.push(level); 105 | i *= 2; 106 | } 107 | 108 | (rtg_indices, rtg_levels) 109 | } 110 | 111 | pub fn pv_decompress(evaluator: &Evaluator, indices_ct: &Ciphertext, sk: &SecretKey) -> Vec<u64> { 112 | let pv = evaluator.plaintext_decode(&evaluator.decrypt(sk, indices_ct), Encoding::default()); 113 | let mut pv_decompressed = vec![]; 114 | 115 | assert!(pv.len() == 32768); 116 | 117 | pv.iter().for_each(|value| { 118 | let mut value = *value; 119 | for _ in 0..16 { 120 | pv_decompressed.push(value & 1); 121 | value >>= 1; 122 | } 123 | }); 124 | 125 | pv_decompressed 126 | } 127 | 128 | pub fn construct_lhs( 129 | pv: &[u64], 130 | buckets: &[Vec<u64>], 131 | weights: &[Vec<u64>], 132 | k: usize, 133 | gamma: usize, 134 | set_size: usize, 135 | ) -> Vec<Vec<u64>> { 136 | let mut lhs = vec![vec![0u64; k]; k * 2]; 137 | let mut curr_col = 0; 138 | for i in 0..set_size { 139 | let value = pv[i]; 140 | assert!(value <= 1); 141 | 142 | if value == 1 { 143 | if curr_col == k { 144 | panic!("Overflow!"); 145 | } 146 | 147 | let row_buckets = &buckets[i]; 148 | let row_weights = &weights[i]; 149 | 150 | for j in 0..gamma { 151 | let b = row_buckets[j]; 152 | let w = row_weights[j]; 153 | lhs[b as usize][curr_col] = w; 154 | } 155 | curr_col += 1; 156 | } 157 | } 158 | lhs 159 | } 160 | 161 | pub fn construct_rhs(weights_vec: &[u64], bucket_size: usize) -> Vec<Vec<u64>> { 162 | weights_vec 163 | .chunks_exact(bucket_size) 164 | .map(|c| c.to_vec()) 165 | .collect_vec() 166 | } 167 | 168 | pub fn scale_vec(scale_by: u64, a: &[u64], modq: &Modulus) -> Vec<u64> { 169 | a.iter() 170 | .map(|v| modq.mul_mod_fast(scale_by, *v)) 171 | .collect_vec() 172 | } 173 | 174 | pub fn print_matrix(m: &Vec<Vec<u64>>, row: usize, col: usize) { 175 | println!("### Matrix ###"); 176 | println!("rows: {row}; cols: {col}"); 177 | for i in 0..row { 178 | println!("{:?}", &m[i][..col]); 179 | } 180 | } 181 | 182 | pub fn solve_equations( 183 | mut lhs: Vec<Vec<u64>>, 184 | mut rhs: Vec<Vec<u64>>, 185 | k: usize, 186 | modq: u64, 187 | ) -> Vec<Vec<u64>> { 188 | // max no of vars 189 | let cols = k; 190 | // no of equations 191 | let rows = k * 2; 192 | 193 | let modq = Modulus::new(modq); 194 | let mut pivot_rows = vec![-1; cols]; 195 | for pi in 0..cols { 196 | for eq in 0..rows { 197 | // find the pivot 198 | if !pivot_rows.contains(&(eq as isize)) { 199 | if (pivot_rows[pi] != -1 && lhs[pivot_rows[pi] as usize][pi] < lhs[eq][pi]) 200 | || (pivot_rows[pi] == -1 && lhs[eq][pi] != 0) 201 | { 202 | pivot_rows[pi] = eq as isize; 203 | } 204 | } 205 | } 206 | 207 | if pivot_rows[pi] == -1 { 208 | break; 209 | } 210 | 211 | let pivot_row = pivot_rows[pi] as usize; 212 | let pivot_value = lhs[pivot_row][pi]; 213 | for r in 0..rows { 214 | if r != pivot_row as usize { 215 | let value = lhs[r][pi]; 216 | 217 | if value == 0 { 218 | continue; 219 | } 220 | 221 | // scale `r`th row by `pivot_value/value` and then subtract `pivot_row` from `r`th to cancel `r`th 222 | // row's coefficient at `pi`th column. 223 | let scale_by = modq.mul_mod_fast(pivot_value, modq.inv(value)); 224 | let mut scaled_lhs = scale_vec(scale_by, &lhs[r], &modq); 225 | modq.sub_mod_fast_vec(&mut scaled_lhs, &lhs[pivot_row]); 226 | lhs[r] = scaled_lhs; 227 | 228 | let mut scaled_rhs = scale_vec(scale_by, &rhs[r], &modq); 229 | modq.sub_mod_fast_vec(&mut scaled_rhs, &rhs[pivot_row]); 230 | rhs[r] = scaled_rhs; 231 | } 232 | } 233 | } 234 | let mut messages = vec![]; 235 | for pi in 0..cols { 236 | if pivot_rows[pi] != -1 { 237 | let row = pivot_rows[pi] as usize; 238 | let col = pi as usize; 239 | let value = lhs[row][col]; 240 | let value_inv = modq.inv(value); 241 | let m = rhs[row] 242 | .iter() 243 | .map(|v| modq.mul_mod_fast(value_inv, *v)) 244 | .collect_vec(); 245 | messages.push(m); 246 | } 247 | } 248 | 249 | messages 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use bfv::Modulus; 255 | use rand::{Rng, SeedableRng}; 256 | use rand_chacha::ChaCha8Rng; 257 | 258 | use crate::{ 259 | client::construct_rhs, preprocessing::assign_buckets_and_weights, 260 | utils::generate_random_payloads, BUCKET_SIZE, GAMMA, K, 261 | }; 262 | 263 | use super::{construct_lhs, solve_equations}; 264 | 265 | #[test] 266 | fn test_solve_linear_equations() { 267 | let mut rng = ChaCha8Rng::from_seed([2; 32]); 268 | let qmod = Modulus::new(65537); 269 | let set_size = 32768; 270 | let bucket_size = BUCKET_SIZE as u64; 271 | let (seed, buckets, weights) = 272 | assign_buckets_and_weights(K * 2, GAMMA, qmod.modulus(), set_size, &mut rng); 273 | 274 | // Randomly generates pertinency vector with K pertinent indices 275 | let mut pv = vec![0u64; set_size]; 276 | let mut pertinent_indices = vec![]; 277 | while pertinent_indices.len() != 64 { 278 | let index = rng.gen::<usize>() % set_size; 279 | if !pertinent_indices.contains(&index) { 280 | pertinent_indices.push(index); 281 | pv[index] = 1; 282 | } 283 | } 284 | pertinent_indices.sort(); 285 | 286 | // randomly generate corresponding data 287 | let payloads = generate_random_payloads(set_size); 288 | 289 | // calculate weight vector 290 | let mut weight_vec = vec![0u64; set_size]; 291 | for lane_index in 0..set_size { 292 | // ignore the ones not pertinent. We can do this here, but not when pv is ciphertext 293 | if pv[lane_index] == 1 { 294 | let row_bucket = &buckets[lane_index]; 295 | let row_weights = &weights[lane_index]; 296 | 297 | for j in 0..GAMMA { 298 | let bucket_index = row_bucket[j]; 299 | let bucket_weight = row_weights[j]; 300 | let bucket_offset = (bucket_index * bucket_size) as usize; 301 | for i in 0..bucket_size as usize { 302 | weight_vec[bucket_offset + i] = qmod.add_mod( 303 | weight_vec[bucket_offset + i], 304 | qmod.mul_mod_fast(bucket_weight, payloads[lane_index][i] as u64), 305 | ); 306 | } 307 | } 308 | } 309 | } 310 | 311 | let lhs = construct_lhs(&pv, &buckets, &weights, K, GAMMA, set_size); 312 | let rhs = construct_rhs(&weight_vec, bucket_size as usize); 313 | 314 | let result = solve_equations(lhs, rhs, K, qmod.modulus()); 315 | let mut expected = vec![]; 316 | pertinent_indices.iter().for_each(|index| { 317 | expected.push(payloads[*index].clone()); 318 | }); 319 | assert_eq!(result, expected); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/server/pvw_decrypt.rs: -------------------------------------------------------------------------------- 1 | use crate::optimised::{coefficient_u128_to_ciphertext, fma_reverse_u128_poly}; 2 | use crate::preprocessing::{precompute_expand_32_roll_pt, procompute_expand_roll_pt}; 3 | use crate::server::powers_x::evaluate_powers; 4 | use crate::{ 5 | optimised::{barret_reduce_coefficients_u128, sub_from_one}, 6 | pvw::PvwParameters, 7 | }; 8 | use bfv::{ 9 | BfvParameters, Ciphertext, EvaluationKey, Evaluator, GaloisKey, Plaintext, Poly, PolyContext, 10 | PolyType, RelinearizationKey, Representation, SecretKey, 11 | }; 12 | use core::time; 13 | use itertools::{izip, Itertools}; 14 | use ndarray::{s, Array, Array2}; 15 | use rand_chacha::rand_core::le; 16 | use rayon::prelude::{ 17 | IndexedParallelIterator, IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator, 18 | }; 19 | 20 | /// Pre-compute rotations of `sk_cts`s such that pvw_decrypt can leverage all avaialble cores. 21 | pub fn pvw_setup( 22 | evaluator: &Evaluator, 23 | ek: &EvaluationKey, 24 | pvw_sk_cts: &[Ciphertext], 25 | ) -> Vec<Vec<Ciphertext>> { 26 | // assumes that threads % 4=0 27 | let threads = rayon::current_num_threads(); 28 | assert!(threads % 4 == 0); 29 | // pvw decrypt is called 4 times with different ciphertexts. Thus, we first assign total no. of threads 30 | // equally among all 4 calls before dividing them further for rotations within each call. 31 | let threads_by_4 = threads as f64 / 4.0; 32 | // We need to distribute the task of 512 rotations among all threads available to a single pvw_decrypt call equally. For ex, if threads_by_4 = 8 33 | // then each thread will perform `512/8=64` rotations. However, rotations will be distributed unequally if 512%threads_by_4 != 0 34 | // and the time will be set by the last thread to which maximum number of rotations are allocated. For ex, let thread_by_4 = 11. 35 | // Since 512%11 != 0, 10 threads will be allocated 46 rotations whereas the last thread will have to do 512-(10*46) = 52 rotations, 36 | // thus defining the total time taken. 37 | let rots_per_thread = (512.0 / threads_by_4).floor() as usize; 38 | 39 | let mut checkpoint_cts = vec![vec![]; 4]; 40 | let mut cts = pvw_sk_cts.to_vec(); 41 | 42 | // verify all cts are in Evaluation representation for efficient rotations and plaintext multiplication in pvw_decrypt 43 | cts.iter() 44 | .for_each(|c| assert!(c.c_ref()[0].representation() == &Representation::Evaluation)); 45 | 46 | for j in 0..threads_by_4 as usize { 47 | // Checkpoints for each thread are cts rotated by j*rots_per_thread. 48 | for i in 0..4 { 49 | checkpoint_cts[i].push(cts[i].clone()); 50 | } 51 | 52 | // No rotations are needed anymore once checkpoints for last thread have been stored. 53 | if j != (threads_by_4 as usize) - 1 { 54 | // rotate the ciphertexts till next checkpoint 55 | cts.par_iter_mut().for_each(|ct| { 56 | for _ in 0..rots_per_thread { 57 | *ct = evaluator.rotate(&ct, 1, ek); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | checkpoint_cts 64 | } 65 | 66 | /// Rotates `s` for `sec_len` times. After i^th rotation multiplies the result with plaintext at i^th index in hint_a_pts and adds the 67 | /// result to final sum. Function takes advantage of the assumption that modulus is smaller than 50 bits to speed up fused mutliplication 68 | /// and additions using 128 bit arithmetic, that is without modulur reduction. Returns coefficients of ciphertext polynomials without modular 69 | /// reduction. 70 | pub fn optimised_pvw_fma_with_rot( 71 | params: &BfvParameters, 72 | s: &Ciphertext, 73 | hint_a_pts: &[Plaintext], 74 | sec_len: usize, 75 | rtg: &GaloisKey, 76 | sk: &SecretKey, 77 | ) -> (Array2<u128>, Array2<u128>) { 78 | debug_assert!(sec_len <= 512); 79 | 80 | let shape = s.c_ref()[0].coefficients().shape(); 81 | let mut d_u128 = ndarray::Array2::<u128>::zeros((shape[0], shape[1])); 82 | let mut d1_u128 = ndarray::Array2::<u128>::zeros((shape[0], shape[1])); 83 | 84 | // To repeatedly rotate `s` and set output to `s`, `s` must be mutable, however the function 85 | // only takes `s` as a reference. Changing to mutable reference is unecessary after realising that 86 | // `rotate` also takes `s` as a reference. Hence, we process the first iteration outside the loop. 87 | fma_reverse_u128_poly(&mut d_u128, &s.c_ref()[0], hint_a_pts[0].mul_poly_ref()); 88 | fma_reverse_u128_poly(&mut d1_u128, &s.c_ref()[1], hint_a_pts[0].mul_poly_ref()); 89 | let mut s = rtg.rotate(s, params); 90 | for i in 1..sec_len { 91 | fma_reverse_u128_poly(&mut d_u128, &s.c_ref()[0], hint_a_pts[i].mul_poly_ref()); 92 | fma_reverse_u128_poly(&mut d1_u128, &s.c_ref()[1], hint_a_pts[i].mul_poly_ref()); 93 | s = rtg.rotate(&s, params); 94 | } 95 | (d_u128, d1_u128) 96 | } 97 | 98 | /// Calls optimised_pvw_fma_with_rot and reduces the 128bit coefficients of ciphertext polynomials using 128 bit barrett reduction 99 | /// and returns the ciphertext. 100 | pub fn optimised_pvw_fma_with_rot_and_reduction( 101 | params: &BfvParameters, 102 | s: &Ciphertext, 103 | hint_a_pts: &[Plaintext], 104 | sec_len: usize, 105 | rtg: &GaloisKey, 106 | sk: &SecretKey, 107 | ) -> Ciphertext { 108 | let (d_u128, d1_u128) = optimised_pvw_fma_with_rot(params, s, hint_a_pts, sec_len, rtg, sk); 109 | coefficient_u128_to_ciphertext(params, &d_u128, &d1_u128, s.level()) 110 | } 111 | 112 | /// pvw_decrypt can only use 4 cores at once. 113 | pub fn pvw_decrypt( 114 | pvw_params: &PvwParameters, 115 | evaluator: &Evaluator, 116 | hint_a_pts: &[Plaintext], 117 | hint_b_pts: &[Plaintext], 118 | pvw_sk_cts: &[Ciphertext], 119 | rtg: &GaloisKey, 120 | sk: &SecretKey, 121 | ) -> Vec<Ciphertext> { 122 | let sec_len = pvw_params.n.next_power_of_two(); 123 | assert!(hint_a_pts.len() == sec_len); 124 | assert!(hint_b_pts.len() == pvw_params.ell); 125 | assert!(pvw_sk_cts.len() == pvw_params.ell); 126 | 127 | let mut sk_a = vec![]; 128 | pvw_sk_cts 129 | .into_par_iter() 130 | .map(|s_ct| { 131 | // s_ct must be in Evaluation for efficient rotations and plaintext multiplication 132 | assert!(s_ct.c_ref()[0].representation() == &Representation::Evaluation); 133 | optimised_pvw_fma_with_rot_and_reduction( 134 | evaluator.params(), 135 | s_ct, 136 | hint_a_pts, 137 | sec_len, 138 | rtg, 139 | sk, 140 | ) 141 | }) 142 | .collect_into_vec(&mut sk_a); 143 | 144 | sk_a.iter_mut().zip(hint_b_pts.iter()).for_each(|(sa, b)| { 145 | evaluator.sub_ciphertext_from_poly_inplace(sa, b.add_sub_poly_ref()); 146 | }); 147 | 148 | sk_a 149 | } 150 | 151 | fn add_array_u128(a: &mut Array2<u128>, b: &Array2<u128>) { 152 | izip!(a.outer_iter_mut(), b.outer_iter()).for_each(|(mut a0, b0)| { 153 | izip!(a0.iter_mut(), b0.iter()).for_each(|(r, s)| { 154 | *r += *s; 155 | }); 156 | }); 157 | } 158 | 159 | /// Assigns precomputed sk_cts and corresponding hint_a_pts to available threads. 160 | /// 161 | /// Recursively calls itself until it narrows down to a single start index. After which it calls 162 | /// `optimised_pvw_fma_with_rot` for sk_ct with corresponding slice of hint_a_pts. Correspondence is 163 | /// determined by the index of sk_ct, which infact means the number of times it has been rotated during pre-computation. For ex, 164 | /// the sk_ct at position 1 must be matched with slice chunk of hint_a_pts offset by `rots_per_thread*1` since 165 | /// first `rots_per_thread` pts are for sk_ct at position 0. 166 | /// 167 | /// Takes care of the case when available threads (ie threads_by_4) does not divide 512 (ie total no. of rotations). 168 | /// For ex, when threads_by_4 = 11, it means there will be 45 rotations on first 10 threads and 62 rotations 169 | /// on the last thread. (Note: no need to handle case threads_by_4%2 !=0 anymore) 170 | fn thread_helper( 171 | size: usize, 172 | params: &BfvParameters, 173 | sk_cts: &[Ciphertext], 174 | pts: &[Plaintext], 175 | rtg: &GaloisKey, 176 | sk: &SecretKey, 177 | ) -> (Array2<u128>, Array2<u128>) { 178 | if sk_cts.len() == 1 { 179 | let s = &sk_cts[0]; 180 | // println!(" len: {}", pts.len()); 181 | optimised_pvw_fma_with_rot(params, s, pts, pts.len(), rtg, sk) 182 | } else { 183 | let mid = sk_cts.len() / 2; 184 | 185 | let (mut r0, r1) = rayon::join( 186 | || thread_helper(size, params, &sk_cts[..mid], &pts[..mid * size], rtg, sk), 187 | || thread_helper(size, params, &sk_cts[mid..], &pts[mid * size..], rtg, sk), 188 | ); 189 | 190 | add_array_u128(&mut r0.0, &r1.0); 191 | add_array_u128(&mut r0.1, &r1.1); 192 | 193 | r0 194 | } 195 | } 196 | 197 | pub fn pvw_decrypt_precomputed( 198 | pvw_params: &PvwParameters, 199 | evaluator: &Evaluator, 200 | hint_a_pts: &[Plaintext], 201 | hint_b_pts: &[Plaintext], 202 | precomputed_pvw_sk_cts: &[Vec<Ciphertext>], 203 | rtg: &GaloisKey, 204 | sk: &SecretKey, 205 | ) -> Vec<Ciphertext> { 206 | let sec_len = pvw_params.n.next_power_of_two(); 207 | assert!(pvw_params.ell == 4); 208 | assert!(hint_a_pts.len() == sec_len); 209 | assert!(hint_b_pts.len() == pvw_params.ell); 210 | assert!(precomputed_pvw_sk_cts.len() == pvw_params.ell); 211 | 212 | let threads = rayon::current_num_threads(); 213 | assert!(threads % 4 == 0); 214 | let threads_by_4 = threads / 4; 215 | // Validate that number of checkpoints are equals disbuted among all threads_by_4. 216 | // This means precomputed rotations of sk_ct must be equal to threads_by_4. 217 | assert!(precomputed_pvw_sk_cts[0].len() == threads_by_4); 218 | assert!(precomputed_pvw_sk_cts[1].len() == threads_by_4); 219 | assert!(precomputed_pvw_sk_cts[2].len() == threads_by_4); 220 | assert!(precomputed_pvw_sk_cts[3].len() == threads_by_4); 221 | 222 | let rots_per_thread = (512.0 / threads_by_4 as f64).floor() as usize; 223 | 224 | let mut sk_a = vec![]; 225 | (0..4) 226 | .into_par_iter() 227 | .map(|index| { 228 | let pool = rayon::ThreadPoolBuilder::new() 229 | .num_threads(threads_by_4) 230 | .build() 231 | .unwrap(); 232 | pool.install(|| { 233 | let (d0, d1) = thread_helper( 234 | rots_per_thread, 235 | evaluator.params(), 236 | &precomputed_pvw_sk_cts[index], 237 | &hint_a_pts, 238 | rtg, 239 | sk, 240 | ); 241 | coefficient_u128_to_ciphertext(evaluator.params(), &d0, &d1, 0) 242 | }) 243 | }) 244 | .collect_into_vec(&mut sk_a); 245 | 246 | sk_a.iter_mut().zip(hint_b_pts.iter()).for_each(|(sa, b)| { 247 | // b - s0 248 | evaluator.sub_ciphertext_from_poly_inplace(sa, b.add_sub_poly_ref()); 249 | }); 250 | 251 | sk_a 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | 257 | fn helper(v: &[u64], pts: &[u64], size: usize) { 258 | if v.len() == 1 { 259 | // let sk_ct = &v[start]; 260 | // let chunk_pts = &pts[start * size..]; 261 | 262 | println!("{}", pts.len()); 263 | } else { 264 | let mid = v.len() / 2; 265 | helper(&v[..mid], &pts[..size * mid], size); 266 | helper(&v[mid..], &pts[size * mid..], size); 267 | } 268 | } 269 | 270 | #[test] 271 | fn caller() { 272 | let v = [0; 5]; 273 | let pts = [0; 512]; 274 | helper(&v, &pts, 102); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/server/range_fn/range_fn_fma.rs: -------------------------------------------------------------------------------- 1 | use crate::optimised::{coefficient_u128_to_ciphertext, fma_reverse_u128_poly}; 2 | use crate::preprocessing::{precompute_expand_32_roll_pt, procompute_expand_roll_pt}; 3 | use crate::{ 4 | optimised::{barret_reduce_coefficients_u128, sub_from_one}, 5 | pvw::PvwParameters, 6 | }; 7 | use bfv::{ 8 | BfvParameters, Ciphertext, EvaluationKey, Evaluator, GaloisKey, Plaintext, Poly, PolyContext, 9 | PolyType, RelinearizationKey, Representation, SecretKey, 10 | }; 11 | use itertools::{izip, Itertools}; 12 | use ndarray::{s, Array2}; 13 | 14 | /// Performs FMA: r0 += a0 * s 15 | pub fn scalar_mul_u128(r: &mut [u128], a: &[u64], s: u64) { 16 | let s_u128 = s as u128; 17 | r.iter_mut().zip(a.iter()).for_each(|(r0, a0)| { 18 | *r0 += *a0 as u128 * s_u128; 19 | }) 20 | } 21 | 22 | /// Performs res[i] += a[i] * scalar[i] where i is index of qi in moduli chain, res is 23 | /// unreduced coefficients corresponding to qi modulus stored in row major form, a[i] are reduced coefficients 24 | /// corresponding to qi modulus stored in row major form, scalar[i] is i^th scalar in scalar_slice. 25 | /// 26 | /// Functions calls `scalar_mul_u128` equivalent to moduli count times. 27 | pub fn mul_poly_scalar_u128(res: &mut Array2<u128>, a: &Poly, scalar_slice: &[u64]) { 28 | izip!( 29 | res.outer_iter_mut(), 30 | a.coefficients().outer_iter(), 31 | scalar_slice.iter(), 32 | ) 33 | .for_each(|(mut r, a, s)| { 34 | scalar_mul_u128(r.as_slice_mut().unwrap(), a.as_slice().unwrap(), *s); 35 | }); 36 | } 37 | 38 | /// Performs inner k loop of range function. More specifically, function calculates res += single_powers[i] * constants[i] 39 | /// where single_powers[i] is i^th k_power and constants[i] is corresponding constant reduced with respect to moduli chain. 40 | /// 41 | /// Function assumes that each qi in moduli chain is <= 50 bits hence modulus reduction can be delayed until last 42 | /// scalar multiplication. Hence modulus vector multiplication and additions are replace with normal vector multiplications, additions. 43 | /// 44 | /// In concrete costs, functions call `mul_poly_scalar_u128` * 128 * 2 times. 45 | pub fn optimised_range_fn_fma_u128( 46 | poly_ctx: &PolyContext<'_>, 47 | params: &BfvParameters, 48 | single_powers: &[Ciphertext], 49 | constants: &Array2<u64>, 50 | constants_outer_offset: usize, 51 | level: usize, 52 | ) -> Ciphertext { 53 | let mut res0 = Array2::<u128>::zeros((poly_ctx.moduli_count(), poly_ctx.degree())); 54 | let mut res1 = Array2::<u128>::zeros((poly_ctx.moduli_count(), poly_ctx.degree())); 55 | 56 | // Starting from 0th index every alternate constant is 0. Since plaintext multiplication by 0 is 0, we don't need to 57 | // process plaintext multiplications for indices at which constant is 0. Thus, we start from 1st index and process 58 | // every alternate index. 59 | for j in (2..257).step_by(2) { 60 | let power_ct = &single_powers[j / 2 - 1]; 61 | let scalar_reduced = constants.slice(s![constants_outer_offset + (j - 1), ..]); 62 | 63 | mul_poly_scalar_u128( 64 | &mut res0, 65 | &power_ct.c_ref()[0], 66 | scalar_reduced.as_slice().unwrap(), 67 | ); 68 | mul_poly_scalar_u128( 69 | &mut res1, 70 | &power_ct.c_ref()[1], 71 | scalar_reduced.as_slice().unwrap(), 72 | ); 73 | } 74 | 75 | coefficient_u128_to_ciphertext(params, &res0, &res1, level) 76 | } 77 | 78 | #[cfg(target_arch = "x86_64")] 79 | /// Performs res[i] = a[i] * scalar[i] where i is index of qi in moduli chain, resulting `res` are 80 | /// reduced coefficients corresponding to qi modulus stored in row major form, a[i] are reduced coefficients 81 | /// corresponding to qi modulus stored in row major form, scalar[i] is i^th scalar in scalar_slice. 82 | /// 83 | /// Use `fma_poly_scale_slice_hexl` over `fma_poly_scale_slice_u128` on x86 since it performs better 84 | /// assuming that each qi in moduli chain is <=50 bit (ie when hexl uses IFMA instruction set) 85 | /// 86 | /// Calls `hexl_rs::elwise_fma_mod` moduli count times. 87 | pub fn mul_poly_scalar_slice_hexl( 88 | poly_ctx: &PolyContext<'_>, 89 | res: &mut Poly, 90 | a: &Poly, 91 | scalar_slice: &[u64], 92 | ) { 93 | izip!( 94 | res.coefficients_mut().outer_iter_mut(), 95 | a.coefficients().outer_iter(), 96 | scalar_slice.iter(), 97 | poly_ctx.moduli_ops().iter() 98 | ) 99 | .for_each(|(mut r, a, s, modqi)| { 100 | let qi = modqi.modulus(); 101 | hexl_rs::elwise_mult_scalar_mod_2( 102 | r.as_slice_mut().unwrap(), 103 | a.as_slice().unwrap(), 104 | *s, 105 | qi, 106 | poly_ctx.degree() as u64, 107 | 1, 108 | ); 109 | }); 110 | } 111 | 112 | /// Performs res[i] += a[i] * scalar[i] where i is index of qi in moduli chain, res are 113 | /// reduced coefficients corresponding to qi modulus stored in row major form, a[i] are reduced coefficients 114 | /// corresponding to qi modulus stored in row major form, scalar[i] is i^th scalar in scalar_slice. 115 | /// 116 | /// Use `fma_poly_scale_slice_hexl` over `fma_poly_scale_slice_u128` on x86 since it performs better 117 | /// assuming that each qi in moduli chain is <=50 bit (ie when hexl uses IFMA instruction set) 118 | /// 119 | /// Calls `hexl_rs::elwise_fma_mod` moduli count times. 120 | #[cfg(target_arch = "x86_64")] 121 | pub fn fma_poly_scale_slice_hexl( 122 | poly_ctx: &PolyContext<'_>, 123 | res: &mut Poly, 124 | a: &Poly, 125 | scalar_slice: &[u64], 126 | ) { 127 | izip!( 128 | res.coefficients_mut().outer_iter_mut(), 129 | a.coefficients().outer_iter(), 130 | scalar_slice.iter(), 131 | poly_ctx.moduli_ops().iter() 132 | ) 133 | .for_each(|(mut r, a, s, modqi)| { 134 | let qi = modqi.modulus(); 135 | hexl_rs::elwise_fma_mod( 136 | r.as_slice_mut().unwrap(), 137 | *s, 138 | a.as_slice().unwrap(), 139 | qi, 140 | poly_ctx.degree() as u64, 141 | 1, 142 | ) 143 | }); 144 | } 145 | 146 | /// Performs inner k loop of range function. More specifically, function calculates res += single_powers[i] * constants[i] 147 | /// where single_powers[i] is i^th k_power and constants[i] is corresponding constant reduced with respect to moduli chain. 148 | /// 149 | /// Use this function over `optimised_range_fn_fma_u128` on x86 if each qi in moduli chain is <= 50 bites since it performs 150 | /// better by using hexl_rs APIs for scalar multplication and fused multiplication and addition. 151 | /// 152 | /// In concrete costs, functions call `mul_poly_scalar_slice_hexl` 2 times and fma_poly_scale_slice_hexl 127 * 2 times. In general 153 | /// costs of `fma_poly_scale_slice_hexl` and `mul_poly_scalar_slice_hexl` can be assumed to be same. 154 | #[cfg(target_arch = "x86_64")] 155 | pub fn optimised_range_fn_fma_hexl( 156 | poly_ctx: &PolyContext<'_>, 157 | single_powers: &[Ciphertext], 158 | constants: &Array2<u64>, 159 | constants_outer_offset: usize, 160 | level: usize, 161 | ) -> Ciphertext { 162 | // process the first index using scalar mod instead of fma, since sum_ct is uinitialised 163 | let mut sum_ct = { 164 | let scalar_slice = constants.slice(s![constants_outer_offset + 1, ..]); 165 | 166 | let mut p0 = Poly::new( 167 | unsafe { 168 | Array2::<u64>::uninit((poly_ctx.moduli_count(), poly_ctx.degree())).assume_init() 169 | }, 170 | Representation::Evaluation, 171 | ); 172 | 173 | mul_poly_scalar_slice_hexl( 174 | poly_ctx, 175 | &mut p0, 176 | &single_powers[0].c_ref()[0], 177 | scalar_slice.as_slice().unwrap(), 178 | ); 179 | 180 | let mut p1 = Poly::new( 181 | unsafe { 182 | Array2::<u64>::uninit((poly_ctx.moduli_count(), poly_ctx.degree())).assume_init() 183 | }, 184 | Representation::Evaluation, 185 | ); 186 | 187 | mul_poly_scalar_slice_hexl( 188 | poly_ctx, 189 | &mut p1, 190 | &single_powers[0].c_ref()[1], 191 | scalar_slice.as_slice().unwrap(), 192 | ); 193 | 194 | Ciphertext::new(vec![p0, p1], PolyType::Q, level) 195 | }; 196 | 197 | for j in (4..257).step_by(2) { 198 | let power_ct = &single_powers[j / 2 - 1]; 199 | let scalar_reduced = constants.slice(s![constants_outer_offset + (j - 1), ..]); 200 | 201 | fma_poly_scale_slice_hexl( 202 | poly_ctx, 203 | &mut sum_ct.c_ref_mut()[0], 204 | &power_ct.c_ref()[0], 205 | scalar_reduced.as_slice().unwrap(), 206 | ); 207 | fma_poly_scale_slice_hexl( 208 | poly_ctx, 209 | &mut sum_ct.c_ref_mut()[1], 210 | &power_ct.c_ref()[1], 211 | scalar_reduced.as_slice().unwrap(), 212 | ); 213 | } 214 | 215 | sum_ct 216 | } 217 | 218 | #[cfg(test)] 219 | mod tests { 220 | use super::*; 221 | use crate::utils::{generate_bfv_parameters, precompute_range_constants, read_range_coeffs}; 222 | use bfv::{Encoding, PolyCache}; 223 | use rand::thread_rng; 224 | 225 | #[test] 226 | fn test_optimised_range_fn_fma() { 227 | let mut rng = thread_rng(); 228 | // let params = BfvParameters::new(&vec![59; 15], 65537, 1 << 15); 229 | let params = generate_bfv_parameters(); 230 | let m = params 231 | .plaintext_modulus_op 232 | .random_vec(params.degree, &mut rng); 233 | let sk = SecretKey::random_with_params(&params, &mut rng); 234 | 235 | let evaluator = Evaluator::new(params); 236 | let pt = evaluator.plaintext_encode(&m, Encoding::simd(0, PolyCache::Mul(PolyType::Q))); 237 | let mut ct = evaluator.encrypt(&sk, &pt, &mut rng); 238 | // change ct representation to Evaluation for plaintext mul 239 | evaluator.ciphertext_change_representation(&mut ct, Representation::Evaluation); 240 | 241 | let ctx = evaluator.params().poly_ctx(&PolyType::Q, 0); 242 | let level = 0; 243 | let single_powers = vec![ct.clone(); 128]; 244 | let constants = precompute_range_constants(&ctx); 245 | 246 | { 247 | // warmup 248 | let mut tmp = evaluator.mul_poly(&ct, pt.mul_poly_ref()); 249 | for j in 0..300 { 250 | evaluator.add_assign(&mut tmp, &evaluator.mul_poly(&ct, pt.mul_poly_ref())); 251 | } 252 | } 253 | 254 | // optimised hexl version 255 | let now = std::time::Instant::now(); 256 | #[cfg(target_arch = "x86_64")] 257 | let res_opt_hexl = optimised_range_fn_fma_hexl(&ctx, &single_powers, &constants, 0, level); 258 | let time_opt_hexl = now.elapsed(); 259 | 260 | // optimised version 261 | let now = std::time::Instant::now(); 262 | let res_opt_u128 = optimised_range_fn_fma_u128( 263 | &ctx, 264 | evaluator.params(), 265 | &single_powers, 266 | &constants, 267 | 0 * 256, 268 | level, 269 | ); 270 | let time_opt = now.elapsed(); 271 | 272 | // unoptimised fma 273 | let range_coeffs = read_range_coeffs(); 274 | // prepare range coefficients plaintext 275 | let pts = (0..256) 276 | .map(|i| { 277 | let c = range_coeffs[i]; 278 | let m = vec![c; ctx.degree()]; 279 | evaluator.plaintext_encode(&m, Encoding::simd(0, PolyCache::Mul(PolyType::Q))) 280 | }) 281 | .collect_vec(); 282 | let now = std::time::Instant::now(); 283 | let mut res_unopt = evaluator.mul_poly(&ct, pts[2].mul_poly_ref()); 284 | for j in (4..257).step_by(2) { 285 | evaluator.add_assign( 286 | &mut res_unopt, 287 | &evaluator.mul_poly(&ct, pts[j - 1].mul_poly_ref()), 288 | ); 289 | } 290 | let time_unopt = now.elapsed(); 291 | 292 | println!( 293 | "Time: Opt={:?}, OptHexl={:?}, UnOpt={:?}", 294 | time_opt, time_opt_hexl, time_unopt 295 | ); 296 | 297 | #[cfg(target_arch = "x86_64")] 298 | println!( 299 | "Noise: Opt={:?}, OptHexl={:?}, UnOpt={:?}", 300 | evaluator.measure_noise(&sk, &res_opt_u128), 301 | evaluator.measure_noise(&sk, &res_opt_hexl), 302 | evaluator.measure_noise(&sk, &res_unopt), 303 | ); 304 | 305 | #[cfg(not(target_arch = "x86_64"))] 306 | println!( 307 | "Noise: Opt={:?}, UnOpt={:?}", 308 | evaluator.measure_noise(&sk, &res_opt_u128), 309 | evaluator.measure_noise(&sk, &res_unopt), 310 | ); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/server/phase2.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::E; 2 | 3 | use crate::optimised::{coefficient_u128_to_ciphertext, fma_reverse_u128_poly}; 4 | use crate::preprocessing::{ 5 | compute_weight_pts, precompute_expand_32_roll_pt, procompute_expand_roll_pt, read_indices_poly, 6 | }; 7 | use crate::{ 8 | optimised::{barret_reduce_coefficients_u128, sub_from_one}, 9 | pvw::PvwParameters, 10 | }; 11 | use crate::{print_noise, BUCKET_SIZE, GAMMA, MESSAGE_BYTES}; 12 | use bfv::{ 13 | BfvParameters, Ciphertext, EvaluationKey, Evaluator, GaloisKey, Plaintext, Poly, PolyType, 14 | RelinearizationKey, Representation, SecretKey, 15 | }; 16 | use itertools::{izip, Itertools}; 17 | use ndarray::{s, Array2}; 18 | use num_traits::ToPrimitive; 19 | use rand_chacha::rand_core::le; 20 | use rayon::prelude::{IndexedParallelIterator, ParallelIterator}; 21 | use rayon::slice::ParallelSlice; 22 | 23 | pub fn phase2_precomputes( 24 | evaluator: &Evaluator, 25 | degree: usize, 26 | level: usize, 27 | ) -> (Vec<Plaintext>, Vec<Plaintext>, Vec<Plaintext>) { 28 | // extract first 32 29 | let pts_32 = precompute_expand_32_roll_pt(degree, evaluator, level); 30 | // pt_4_roll must be 2d vector that extracts 1st 4, 2nd 4, 3rd 4, and 4th 4. 31 | let pts_4_roll = procompute_expand_roll_pt(32, 4, degree, evaluator, level); 32 | // pt_1_roll must be 2d vector that extracts 1st 1, 2nd 1, 3rd 1, and 4th 1. 33 | let pts_1_roll = procompute_expand_roll_pt(4, 1, degree, evaluator, level); 34 | 35 | (pts_32, pts_4_roll, pts_1_roll) 36 | } 37 | 38 | /// Calculates and returns dot product of cts and polys. 39 | /// 40 | /// res0 += cts[i].c0 * poly 41 | /// res1 += cts[i].c1 * poly 42 | /// 43 | /// Assumes that each qi in moduli chain is <= 50 bits. Leverages this to 44 | /// replace modulur vec multiplication with simply vector multiplication 45 | /// and delays modular reduction until end. 46 | /// 47 | /// Returns result without modulur reduction. 48 | pub fn fma_poly( 49 | ek: &EvaluationKey, 50 | evaluator: &Evaluator, 51 | cts: &[Ciphertext], 52 | polys: &[Poly], 53 | sk: &SecretKey, 54 | level: usize, 55 | ) -> (Array2<u128>, Array2<u128>) { 56 | let coeff_shape = cts 57 | .first() 58 | .unwrap() 59 | .c_ref() 60 | .first() 61 | .unwrap() 62 | .coefficients() 63 | .shape(); 64 | 65 | let mut res0 = Array2::<u128>::zeros((coeff_shape[0], coeff_shape[1])); 66 | let mut res1 = Array2::<u128>::zeros((coeff_shape[0], coeff_shape[1])); 67 | 68 | izip!(cts.iter(), polys.iter()).for_each(|(o, i)| { 69 | // indices 70 | fma_reverse_u128_poly(&mut res0, &o.c_ref()[0], i); 71 | fma_reverse_u128_poly(&mut res1, &o.c_ref()[1], i); 72 | }); 73 | 74 | (res0, res1) 75 | } 76 | 77 | /// a += b 78 | pub fn add_u128_array(a: &mut Array2<u128>, b: &Array2<u128>) { 79 | izip!(a.outer_iter_mut(), b.outer_iter(),).for_each(|(mut ac, bc)| { 80 | izip!(ac.iter_mut(), bc.iter()).for_each(|(v0, v1)| { 81 | *v0 += *v1; 82 | }); 83 | }); 84 | } 85 | 86 | /// A single set of 32 lanes is expanded into 32 ciphertexts where each lane in new ciphertext at index i is equal to corresponding lane at index i in 87 | /// original ciphertext. 88 | /// 89 | /// pv_ct is expanded in set of 32 lanes. Hence each set outputs 32 ciphertext. For a batch of size `batch_size` function returns 90 | /// `32*batch_size` ciphertexts. 91 | pub fn pv_expand_batch( 92 | ek: &EvaluationKey, 93 | evaluator: &Evaluator, 94 | pts_4_roll: &[Plaintext], 95 | pts_1_roll: &[Plaintext], 96 | pv_ct: &Ciphertext, 97 | pts_32: &[Plaintext], 98 | sk: &SecretKey, 99 | ) -> Vec<Ciphertext> { 100 | // let now = std::time::Instant::now(); 101 | 102 | let degree = evaluator.params().degree as isize; 103 | let mut ones = vec![]; 104 | pts_32.iter().enumerate().for_each(|(batch_index, pt_32)| { 105 | // dbg!(pv_ct.level(), pt_32.encoding.as_ref().unwrap().level); 106 | let mut r32_ct = evaluator.mul_poly(pv_ct, pt_32.mul_poly_ref()); 107 | 108 | // populate 32 across all lanes 109 | let mut i = 32; 110 | while i < (degree / 2) { 111 | // rot_count += 1; 112 | let tmp = evaluator.rotate(&r32_ct, i, ek); 113 | evaluator.add_assign(&mut r32_ct, &tmp); 114 | i *= 2; 115 | } 116 | let tmp = evaluator.rotate(&r32_ct, 2 * degree - 1, ek); 117 | evaluator.add_assign(&mut r32_ct, &tmp); 118 | 119 | // extract sets of 4 120 | let mut fours = vec![]; 121 | for i in 0..8 { 122 | fours.push(evaluator.mul_poly(&r32_ct, pts_4_roll[i].mul_poly_ref())); 123 | } 124 | 125 | // expand each set of 4 across all lanes 126 | let mut i = 4; 127 | while i < 32 { 128 | for j in 0..8 { 129 | // rot_count += 1; 130 | let tmp = evaluator.rotate(&mut fours[j], i, ek); 131 | evaluator.add_assign(&mut fours[j], &tmp); 132 | } 133 | i *= 2; 134 | } 135 | 136 | // extract ones 137 | for i in 0..8 { 138 | let four = &fours[i]; 139 | for j in 0..4 { 140 | ones.push(evaluator.mul_poly(four, pts_1_roll[j].mul_poly_ref())); 141 | } 142 | } 143 | 144 | // expand ones across all lanes 145 | let mut i = 1; 146 | while i < 4 { 147 | for j in (batch_index * 32)..(batch_index + 1) * 32 { 148 | // rot_count += 1; 149 | let tmp = evaluator.rotate(&ones[j], i, ek); 150 | evaluator.add_assign(&mut ones[j], &tmp); 151 | } 152 | i *= 2; 153 | } 154 | // dbg!(ones.first().unwrap().c_ref()[0].coefficients.shape()[0]); 155 | }); 156 | 157 | print_noise!( 158 | println!("pv_expand_batch ones[0] noise: {}", evaluator.measure_noise(sk, ones.first().unwrap())); 159 | println!("pv_expand_batch ones[-1] noise: {}", evaluator.measure_noise(sk, ones.last().unwrap())); 160 | ); 161 | 162 | // println!( 163 | // "Pv expand took for batch_size {}: {:?};", 164 | // pts_32.len(), 165 | // now.elapsed(), 166 | // ); 167 | 168 | ones 169 | } 170 | 171 | /// Expands the batch of lanes in pv_ct into individual ciphertexts. Then multiplies each ciphertext with corresponding 172 | /// index plaintext and weight plaintext and then adds all products into 2 ciphertexts, 1 for indices and 1 for weights. 173 | pub fn process_pv_batch( 174 | evaluator: &Evaluator, 175 | ek: &EvaluationKey, 176 | pv_ct: &Ciphertext, 177 | pts_4_roll: &[Plaintext], 178 | pts_1_roll: &[Plaintext], 179 | pts_32_batch: &[Plaintext], 180 | payloads: &[Vec<u64>], 181 | start: usize, 182 | end: usize, 183 | level: usize, 184 | buckets: &[Vec<u64>], 185 | weights: &[Vec<u64>], 186 | sk: &SecretKey, 187 | ) -> ((Array2<u128>, Array2<u128>), (Array2<u128>, Array2<u128>)) { 188 | // println!("Processing batches: {start} - {end}"); 189 | 190 | // expand batch cts 191 | let expanded_cts = pv_expand_batch( 192 | ek, 193 | evaluator, 194 | &pts_4_roll, 195 | &pts_1_roll, 196 | &pv_ct, 197 | pts_32_batch, 198 | &sk, 199 | ); 200 | 201 | assert!(expanded_cts.len() == (end - start) * 32); 202 | 203 | let start_lane = start * 32; 204 | let end_lane = end * 32; 205 | 206 | // println!("Reading indices poly for lanes {start_lane} to {end_lane}..."); 207 | 208 | // Note that indices_poly current computes the poly at runtime. However these polynomials do not change 209 | // across multiple runs. Hence can be pre-computed at stored, replacing expensive NTTs with read operations. 210 | let indices_poly = read_indices_poly(evaluator, level, start_lane, end_lane); 211 | 212 | // Like indices_poly weight_polys can also be pre-computed and stored but with few caveats. 213 | // 1. Pre-computation is limited to each batch. That means for every new batch of 32768 messages we require 214 | // new set of weight_polys. This is because same `seed` used for assigning each index to different buckets 215 | // cannot be used across different batches. Otherwise, the notion that message board is contructed randomly 216 | // will be violated since the server will have to make the seed public for previous batch and an attacker 217 | // can send messages to message board such that system of linear equations are unsolvable. 218 | // 2. Since pre-computation is limited to batch of 32768 we cannot process phase2 across multiple 219 | // batches. I think any practical deployment of OMR will require separating phase 1 and phase 2 and allowing 220 | // users to request messages in a dynamic range. For example, user can request message in range 60,000 and 100,000. 221 | // Hence, at least in any real life scenario pre-computing weight_polys will be useless. 222 | let weights_poly = compute_weight_pts( 223 | evaluator, 224 | level, 225 | payloads, 226 | start_lane, 227 | end_lane, 228 | BUCKET_SIZE, 229 | buckets, 230 | weights, 231 | GAMMA, 232 | ); 233 | 234 | let indices_res = fma_poly(ek, evaluator, &expanded_cts, &indices_poly, sk, level); 235 | let weights_res = fma_poly(ek, evaluator, &expanded_cts, &weights_poly, sk, level); 236 | 237 | (indices_res, weights_res) 238 | } 239 | 240 | /// Divides pts_32 among available threads equally and calls `process_pv_batch` for each chunk (ie batch). 241 | /// 242 | /// Warning: available threads must be power of 2 for maximum core usage. 243 | fn helper( 244 | start: usize, 245 | end: usize, 246 | set_len: usize, 247 | pts_32: &[Plaintext], 248 | evaluator: &Evaluator, 249 | ek: &EvaluationKey, 250 | pv_ct: &Ciphertext, 251 | pts_4_roll: &[Plaintext], 252 | pts_1_roll: &[Plaintext], 253 | payloads: &[Vec<u64>], 254 | level: usize, 255 | buckets: &[Vec<u64>], 256 | weights: &[Vec<u64>], 257 | sk: &SecretKey, 258 | ) -> ((Array2<u128>, Array2<u128>), (Array2<u128>, Array2<u128>)) { 259 | if (end - start) <= set_len { 260 | let res = process_pv_batch( 261 | evaluator, 262 | ek, 263 | pv_ct, 264 | &pts_4_roll, 265 | &pts_1_roll, 266 | &pts_32[start..end], 267 | payloads, 268 | start, 269 | end, 270 | level, 271 | buckets, 272 | weights, 273 | sk, 274 | ); 275 | res 276 | } else { 277 | let mid = (start + end) / 2; 278 | let (mut r0, r1) = rayon::join( 279 | || { 280 | helper( 281 | start, mid, set_len, pts_32, evaluator, ek, pv_ct, pts_4_roll, pts_1_roll, 282 | payloads, level, buckets, weights, sk, 283 | ) 284 | }, 285 | || { 286 | helper( 287 | mid, end, set_len, pts_32, evaluator, ek, pv_ct, pts_4_roll, pts_1_roll, 288 | payloads, level, buckets, weights, sk, 289 | ) 290 | }, 291 | ); 292 | 293 | // add indices polys 294 | // c0 295 | add_u128_array(&mut r0.0 .0, &r1.0 .0); 296 | // c1 297 | add_u128_array(&mut r0.0 .1, &r1.0 .1); 298 | 299 | // add weights polys 300 | // c0 301 | add_u128_array(&mut r0.1 .0, &r1.1 .0); 302 | // c1 303 | add_u128_array(&mut r0.1 .1, &r1.1 .1); 304 | 305 | r0 306 | } 307 | } 308 | 309 | pub fn phase2( 310 | evaluator: &Evaluator, 311 | pv_ct: &Ciphertext, 312 | ek: &EvaluationKey, 313 | level: usize, 314 | pts_32: &[Plaintext], 315 | pts_4_roll: &[Plaintext], 316 | pts_1_roll: &[Plaintext], 317 | payloads: &[Vec<u64>], 318 | buckets: &[Vec<u64>], 319 | weights: &[Vec<u64>], 320 | sk: &SecretKey, 321 | ) -> (Ciphertext, Ciphertext) { 322 | // pertinency vector ciphertext must be in Evaluation representation 323 | assert!(pv_ct.c_ref()[0].representation() == &Representation::Evaluation); 324 | 325 | let num_threads = rayon::current_num_threads() as f64; 326 | let set_len = (pts_32.len() as f64 / num_threads) 327 | .ceil() 328 | .to_usize() 329 | .unwrap(); 330 | 331 | let (indices_poly_u128, weights_poly_u128) = helper( 332 | 0, 333 | pts_32.len(), 334 | set_len, 335 | pts_32, 336 | evaluator, 337 | ek, 338 | pv_ct, 339 | pts_4_roll, 340 | pts_1_roll, 341 | payloads, 342 | level, 343 | buckets, 344 | weights, 345 | sk, 346 | ); 347 | 348 | let indices_ct = coefficient_u128_to_ciphertext( 349 | evaluator.params(), 350 | &indices_poly_u128.0, 351 | &indices_poly_u128.1, 352 | level, 353 | ); 354 | 355 | let weights_ct = coefficient_u128_to_ciphertext( 356 | evaluator.params(), 357 | &weights_poly_u128.0, 358 | &weights_poly_u128.1, 359 | level, 360 | ); 361 | 362 | (indices_ct, weights_ct) 363 | } 364 | 365 | #[cfg(test)] 366 | mod tests { 367 | 368 | use super::*; 369 | use crate::{ 370 | client::gen_pv_exapnd_rtgs, 371 | optimised::{coefficient_u128_to_ciphertext, sub_from_one_precompute}, 372 | plaintext::powers_of_x_modulus, 373 | preprocessing::{assign_buckets_and_weights, precompute_indices_pts}, 374 | utils::{generate_bfv_parameters, generate_random_payloads, precompute_range_constants}, 375 | K, 376 | }; 377 | use bfv::{BfvParameters, Encoding}; 378 | use rand::thread_rng; 379 | 380 | #[test] 381 | fn test_phase2() { 382 | let mut rng = thread_rng(); 383 | let params = generate_bfv_parameters(); 384 | let sk = SecretKey::random_with_params(&params, &mut rng); 385 | 386 | let m = (0..params.degree) 387 | .into_iter() 388 | .map(|index| index as u64) 389 | .collect_vec(); 390 | let evaluator = Evaluator::new(params); 391 | 392 | let mut level = 0; 393 | 394 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 395 | let mut ct = evaluator.encrypt(&sk, &pt, &mut rng); 396 | 397 | level = 12; 398 | evaluator.mod_down_level(&mut ct, level); 399 | 400 | evaluator.ciphertext_change_representation(&mut ct, Representation::Evaluation); 401 | 402 | // Generator rotation keys 403 | let mut ek = gen_pv_exapnd_rtgs(evaluator.params(), &sk, level, &mut rng); 404 | 405 | // pre-computes 406 | let (pts_32_batch, pts_4_roll, pts_1_roll) = 407 | phase2_precomputes(&evaluator, evaluator.params().degree, level); 408 | let (_, buckets, weights) = assign_buckets_and_weights( 409 | K * 2, 410 | GAMMA, 411 | evaluator.params().plaintext_modulus, 412 | evaluator.params().degree, 413 | &mut rng, 414 | ); 415 | let payloads = generate_random_payloads(evaluator.params().degree); 416 | 417 | // restrict to single batch 418 | let pts_32_batch = pts_32_batch[..4].to_vec(); 419 | 420 | let (indices_ct, weights_ct) = phase2( 421 | &evaluator, 422 | &ct, 423 | &ek, 424 | level, 425 | &pts_32_batch, 426 | &pts_4_roll, 427 | &pts_1_roll, 428 | &payloads, 429 | &buckets, 430 | &weights, 431 | &sk, 432 | ); 433 | 434 | dbg!(evaluator.measure_noise(&sk, &indices_ct)); 435 | dbg!(evaluator.measure_noise(&sk, &weights_ct)); 436 | } 437 | 438 | #[test] 439 | fn test_pv_expand_batch() { 440 | let mut rng = thread_rng(); 441 | let params = generate_bfv_parameters(); 442 | let sk = SecretKey::random_with_params(&params, &mut rng); 443 | let degree = params.degree; 444 | let m = vec![3; params.degree]; 445 | 446 | let level = 12; 447 | 448 | let evaluator = Evaluator::new(params); 449 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 450 | let mut ct = evaluator.encrypt(&sk, &pt, &mut rng); 451 | evaluator.mod_down_level(&mut ct, level); 452 | evaluator.ciphertext_change_representation(&mut ct, Representation::Evaluation); 453 | 454 | let (pts_32_batch, pts_4_roll, pts_1_roll) = 455 | phase2_precomputes(&evaluator, evaluator.params().degree, level); 456 | 457 | // restrict to single batch 458 | let pts_32_batch = pts_32_batch[..1].to_vec(); 459 | 460 | let ek = gen_pv_exapnd_rtgs(evaluator.params(), &sk, level, &mut rng); 461 | let ones = pv_expand_batch( 462 | &ek, 463 | &evaluator, 464 | &pts_4_roll, 465 | &pts_1_roll, 466 | &ct, 467 | &pts_32_batch, 468 | &sk, 469 | ); 470 | 471 | assert!(pts_32_batch.len() * 32 == ones.len()); 472 | 473 | ones.iter().for_each(|ct| { 474 | dbg!(evaluator.measure_noise(&sk, ct)); 475 | let rm = evaluator.plaintext_decode(&evaluator.decrypt(&sk, ct), Encoding::default()); 476 | // dbg!(&rm); 477 | assert!(rm == m); 478 | }); 479 | } 480 | 481 | #[test] 482 | pub fn dummy_rot_count() { 483 | let degree = 1 << 15; 484 | let mut rot_count = 0; 485 | 486 | for index in 0..(degree / 32) { 487 | // populate 32 across all lanes 488 | let mut i = 32; 489 | while i < (degree / 2) { 490 | // expansion 491 | rot_count += 1; 492 | i *= 2; 493 | } 494 | rot_count += 1; 495 | 496 | // expand fours 497 | let mut i = 4; 498 | while i < 32 { 499 | for j in 0..8 { 500 | rot_count += 1; 501 | } 502 | i *= 2; 503 | } 504 | 505 | for i in 0..8 { 506 | let mut j = 1; 507 | while j < 4 { 508 | for k in 0..4 { 509 | rot_count += 1; 510 | } 511 | j *= 2; 512 | } 513 | } 514 | } 515 | 516 | println!("Rot count: {rot_count}"); 517 | } 518 | 519 | fn reverse() {} 520 | } 521 | -------------------------------------------------------------------------------- /src/server/range_fn/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::server::powers_x::evaluate_powers; 2 | use crate::{level_down, time_it}; 3 | use crate::{optimised::sub_from_one, print_noise}; 4 | use bfv::{ 5 | Ciphertext, EvaluationKey, Evaluator, GaloisKey, Plaintext, Poly, PolyContext, PolyType, 6 | RelinearizationKey, Representation, SecretKey, 7 | }; 8 | use itertools::Itertools; 9 | use ndarray::Array2; 10 | use rayon::prelude::{IntoParallelRefMutIterator, ParallelIterator}; 11 | 12 | pub mod range_fn_fma; 13 | 14 | /// Helper function that returns sqaure of the `ct` 15 | fn ciphertext_square_and_relin( 16 | evaluator: &Evaluator, 17 | ek: &EvaluationKey, 18 | ct: &Ciphertext, 19 | ) -> Ciphertext { 20 | evaluator.relinearize(&evaluator.mul(ct, ct), ek) 21 | } 22 | 23 | /// Helper function to equally distribute 255 m loop iterations between available threads. 24 | /// Functions starts with range [1, 256) recursively divides the range in half until the distance 25 | /// of range is <= set_len, after which processes loop iterations in the range. 26 | /// set_len is equal to `ceil(255/no_of_threads)`. Thus `set_len` defines appropriate number of iterations 27 | /// that must be assigned to each thread. 28 | fn process_m_loop( 29 | evaluator: &Evaluator, 30 | q_ctx: &PolyContext<'_>, 31 | level: usize, 32 | constants: &Array2<u64>, 33 | k_powers: &[Ciphertext], 34 | m_powers: &[Ciphertext], 35 | set_len: usize, 36 | start: usize, 37 | end: usize, 38 | ) -> Ciphertext { 39 | // process k loop when range is either equal or smaller than set_len 40 | if end - start <= set_len { 41 | // println!("{start} {end} {}", end - start); 42 | 43 | let mut sum = Ciphertext::new(vec![], PolyType::Q, 0); 44 | for i in start..end { 45 | #[cfg(target_arch = "x86_64")] 46 | let res_ct = range_fn_fma::optimised_range_fn_fma_hexl( 47 | &q_ctx, 48 | &k_powers, 49 | &constants, 50 | 256 * i, 51 | level, 52 | ); 53 | 54 | #[cfg(not(target_arch = "x86_64"))] 55 | let res_ct = range_fn_fma::optimised_range_fn_fma_u128( 56 | &q_ctx, 57 | evaluator.params(), 58 | &k_powers, 59 | &constants, 60 | 256 * i, 61 | level, 62 | ); 63 | 64 | // `res_ct` is in `Evaluation` and `m_powers` is in `Coefficient` representation. This works since 65 | // `mul_lazy` accepts different representations if lhs is Evaluation and rhs is in Coefficient 66 | // because the performance is equivalent to when both are in `Coefficient`. 67 | // It's ok to index by `i - 1` here since `start` is never 0 68 | let product = evaluator.mul_lazy(&res_ct, &m_powers[i - 1]); 69 | 70 | // Don't add in first iteration, that is when i == start, since sum is empty 71 | if i == start { 72 | sum = product; 73 | } else { 74 | evaluator.add_assign(&mut sum, &product); 75 | } 76 | } 77 | 78 | sum 79 | } else { 80 | let mid = (start + end) / 2; 81 | let (mut ct0, ct1) = rayon::join( 82 | || { 83 | process_m_loop( 84 | evaluator, q_ctx, level, constants, k_powers, m_powers, set_len, start, mid, 85 | ) 86 | }, 87 | || { 88 | process_m_loop( 89 | evaluator, q_ctx, level, constants, k_powers, m_powers, set_len, mid, end, 90 | ) 91 | }, 92 | ); 93 | evaluator.add_assign(&mut ct0, &ct1); 94 | ct0 95 | } 96 | } 97 | 98 | pub fn range_fn( 99 | ct: &Ciphertext, 100 | evaluator: &Evaluator, 101 | ek: &EvaluationKey, 102 | constants: &Array2<u64>, 103 | sub_from_one_precompute: &[u64], 104 | sk: &SecretKey, 105 | ) -> Ciphertext { 106 | let placeholder = Ciphertext::new(vec![], PolyType::Q, 0); 107 | 108 | // Calculate base powers for k_powers. There's no harm in doing this here since 109 | // evaluate_powers calculates base powers serially as well. 110 | // The intention with doing this here is to evaluate k_powers and m_powers in parallel using `join`. 111 | // Calculate only even powers in range [1,256] 112 | let mut k_powers = vec![placeholder.clone(); 128]; 113 | // calcuate x^2 separately to simplify the code 114 | k_powers[0] = ciphertext_square_and_relin(evaluator, ek, ct); 115 | 116 | level_down!( evaluator.mod_down_next(&mut k_powers[0]);); 117 | 118 | print_noise!( 119 | println!( 120 | "k_powers base 2 noise: {}", 121 | evaluator.measure_noise(sk, &k_powers[0]) 122 | ); 123 | ); 124 | 125 | for base in [4, 8, 16, 32, 64, 128, 256] { 126 | k_powers[(base >> 1) - 1] = 127 | ciphertext_square_and_relin(evaluator, ek, &k_powers[(base >> 2) - 1]); 128 | 129 | level_down!(if base == 8 || base == 32 || base == 64 || base == 256 { 130 | evaluator.mod_down_next(&mut k_powers[(base >> 1) - 1]); 131 | }); 132 | 133 | print_noise!( 134 | println!( 135 | "k_powers base {base} noise: {}", 136 | evaluator.measure_noise(sk, &k_powers[(base >> 1) - 1]) 137 | ); 138 | ); 139 | } 140 | 141 | // calculate all powers in range [1,255] 142 | let mut m_powers = vec![placeholder.clone(); 255]; 143 | // since m^1 = x^256, set k[127] at index 0. 144 | // Although m_powers[0] is equal to k_powers[127] it is neccessary to have two separate copies since we require the same ciphertext 145 | // in different representations. k_powers[127] must be in `Evaluation` representation for efficient plaintext multiplication in inner loop 146 | // and m_powers[0] must `Coefficient` representation for efficient evaluation of m_powers and outer loop muliplication in second m loop interation. 147 | m_powers[0] = { 148 | // We cannot directly call clone, since `mod_down_next` does not free up memory allocated to already dropped rows of fresh ciphertext. 149 | // Calling clone will clone unecessary values causing unecessary memory allocations. Instead we will have to call 150 | // `to_owned` on coefficient arrays owned by polynomials inside ciphertext, to make sure no additional space 151 | // is occupied by not in use rows. 152 | let c_vec = k_powers[127] 153 | .c_ref() 154 | .iter() 155 | .map(|p| { 156 | let coeffs = p.coefficients().to_owned(); 157 | Poly::new(coeffs, p.representation().clone()) 158 | }) 159 | .collect_vec(); 160 | 161 | Ciphertext::new(c_vec, k_powers[127].poly_type(), k_powers[127].level()) 162 | }; 163 | 164 | rayon::join( 165 | || { 166 | // For k_powers we only need to calculate even powers in the range [1,256]. Recall that 167 | // all even numbers in a given range can be obtained by multiplying 2 by all numbers in half of the range. 168 | // For example, all even numbers in range [1,256] can be obtained by multiplying all values in 169 | // range [1,128] by 2. Thus to calculate only even powers in range [0, 256] we calculate all powers 170 | // of `a` in range [1,128] where `a=x^2`. 171 | evaluate_powers(evaluator, ek, 2, 4, &mut k_powers, true, sk); 172 | evaluate_powers(evaluator, ek, 4, 8, &mut k_powers, true, sk); 173 | evaluate_powers(evaluator, ek, 8, 16, &mut k_powers, true, sk); 174 | evaluate_powers(evaluator, ek, 16, 32, &mut k_powers, true, sk); 175 | evaluate_powers(evaluator, ek, 32, 64, &mut k_powers, true, sk); 176 | evaluate_powers(evaluator, ek, 64, 128, &mut k_powers, true, sk); 177 | }, 178 | || { 179 | evaluate_powers(evaluator, ek, 2, 4, &mut m_powers, false, sk); 180 | evaluate_powers(evaluator, ek, 4, 8, &mut m_powers, false, sk); 181 | evaluate_powers(evaluator, ek, 8, 16, &mut m_powers, false, sk); 182 | evaluate_powers(evaluator, ek, 16, 32, &mut m_powers, false, sk); 183 | evaluate_powers(evaluator, ek, 32, 64, &mut m_powers, false, sk); 184 | evaluate_powers(evaluator, ek, 64, 128, &mut m_powers, false, sk); 185 | evaluate_powers(evaluator, ek, 128, 256, &mut m_powers, false, sk); 186 | }, 187 | ); 188 | 189 | // don't re-declare match level inside `level_down` since the macro creates a new scope 190 | let mut match_level = 0; 191 | level_down!( 192 | // lose another level from m_powers to reduce noise for range (128,255]. Then 193 | // match level of all ciphertexts in m_powers and k_powers 194 | match_level = m_powers.last().unwrap().level() + 1; 195 | assert!(match_level == 10); 196 | 197 | k_powers.par_iter_mut().for_each(|mut c| { 198 | // mod down before changing representation 199 | evaluator.mod_down_level(&mut c, match_level); 200 | 201 | // change k_powers to `Evaluation` for efficient plaintext multiplication 202 | evaluator.ciphertext_change_representation(&mut c, Representation::Evaluation); 203 | }); 204 | 205 | m_powers.par_iter_mut().for_each(|mut c| { 206 | // mod down before changing representation 207 | evaluator.mod_down_level(&mut c, match_level); 208 | }); 209 | ); 210 | 211 | #[cfg(not(feature = "level"))] 212 | { 213 | k_powers.par_iter_mut().for_each(|mut c| { 214 | // change k_powers to `Evaluation` for efficient plaintext multiplication 215 | evaluator.ciphertext_change_representation(&mut c, Representation::Evaluation); 216 | }); 217 | } 218 | 219 | let level = match_level; 220 | 221 | let q_ctx = evaluator.params().poly_ctx(&PolyType::Q, level); 222 | 223 | let threads = rayon::current_num_threads() as f64; 224 | // k loop needs to run 255 times. `set_len` fairly distributes 255 iterations among available threads. 225 | let set_len = (255.0 / threads).ceil() as usize; 226 | 227 | // calculate degree [1..256], ie the first k loop, seprarately since it does not 228 | // needs to be multiplied with any m_power and would remain in Q basis. 229 | let m_0th_loop = { 230 | #[cfg(target_arch = "x86_64")] 231 | let mut res_ct = 232 | range_fn_fma::optimised_range_fn_fma_hexl(&q_ctx, &k_powers, &constants, 0, level); 233 | 234 | #[cfg(not(target_arch = "x86_64"))] 235 | let mut res_ct = range_fn_fma::optimised_range_fn_fma_u128( 236 | &q_ctx, 237 | evaluator.params(), 238 | &k_powers, 239 | &constants, 240 | 0, 241 | level, 242 | ); 243 | 244 | // change representation to Coefficient to stay consistent with output of rest of the m loops 245 | evaluator.ciphertext_change_representation(&mut res_ct, Representation::Coefficient); 246 | res_ct 247 | }; 248 | 249 | // process_m_loop processes m^th loop for values in range [start, end) 250 | let mut sum_ct = process_m_loop( 251 | evaluator, &q_ctx, level, constants, &k_powers, &m_powers, set_len, 1, 256, 252 | ); 253 | 254 | // `sum_ct` is in PQ basis, instead of usual Q basis. Call `scale_and_round` to scale 255 | // chiphertext by P/t and switch to Q basis. 256 | let sum_ct = evaluator.scale_and_round(&mut sum_ct); 257 | let mut sum_ct = evaluator.relinearize(&sum_ct, ek); 258 | 259 | // add output of first loop, processed separately, to summation of output of rest of the loops 260 | evaluator.add_assign(&mut sum_ct, &m_0th_loop); 261 | 262 | sub_from_one(evaluator.params(), &mut sum_ct, sub_from_one_precompute); 263 | sum_ct 264 | } 265 | 266 | /// Helper function to call range_fn 4 times with multiple threads. 267 | /// Creates and installs thread pools for each range_fn such that 268 | /// global thread pool is distributed equally among the 4 range_fn calls. 269 | /// Assumes that total_threads is power of 2. 270 | pub fn range_fn_4_times( 271 | decrypted_cts: &[Ciphertext], 272 | evaluator: &Evaluator, 273 | ek: &EvaluationKey, 274 | constants: &Array2<u64>, 275 | sub_from_one_precompute: &[u64], 276 | sk: &SecretKey, 277 | ) -> ((Ciphertext, Ciphertext), (Ciphertext, Ciphertext)) { 278 | // We assume that total no of threads is power of two 279 | let total_threads = rayon::current_num_threads(); 280 | 281 | macro_rules! call_range { 282 | ($index:literal) => { 283 | range_fn( 284 | &decrypted_cts[$index], 285 | evaluator, 286 | ek, 287 | constants, 288 | sub_from_one_precompute, 289 | sk, 290 | ) 291 | }; 292 | } 293 | 294 | if total_threads == 1 { 295 | // there's only 1 thread. Process all 4 calls serially. 296 | let ranged_cts = ( 297 | (call_range!(0), call_range!(1)), 298 | (call_range!(2), call_range!(3)), 299 | ); 300 | return ranged_cts; 301 | } else { 302 | // Divide all threads among 4 range_fn calls. Assuming total_threads is power of 2, `threads_by_4` 303 | // will be >= 1 whenever total_threads != 2. When thread_by_4 >= 1 we can easily assign `threads_by_4` threads 304 | // to each of the 4 range_fn calls and process them in parallel. When thread_by_4 == 0 (ie total_thread = 2) 305 | // we need to group two range_fn calls together and assign a single thread to each of the two group. 306 | let threads_by_4 = total_threads / 4; 307 | // As explained, threads to install will be 1 if there are only 2 threads in total and 4 range_fn calls 308 | // are grouped into 2 of 2. Otherwise install as many as `threads_by_4` threads. 309 | let threads = if threads_by_4 == 0 { 1 } else { threads_by_4 }; 310 | 311 | macro_rules! install_pool_and_call_range_fn { 312 | ($block:tt) => {{ 313 | let pool = rayon::ThreadPoolBuilder::new() 314 | .num_threads(threads) 315 | .build() 316 | .unwrap(); 317 | 318 | pool.install(|| { 319 | // println!("Running {} with {threads} threads...", $index); 320 | $block 321 | }) 322 | }}; 323 | } 324 | 325 | let ranged_cts = if threads_by_4 == 0 { 326 | // case when total_threads = 2 327 | rayon::join( 328 | || install_pool_and_call_range_fn!((call_range!(0), call_range!(1))), 329 | || install_pool_and_call_range_fn!((call_range!(2), call_range!(3))), 330 | ) 331 | } else { 332 | // case when total_threads >= 4 333 | rayon::join( 334 | || { 335 | rayon::join( 336 | || install_pool_and_call_range_fn!({ call_range!(0) }), 337 | || install_pool_and_call_range_fn!({ call_range!(1) }), 338 | ) 339 | }, 340 | || { 341 | rayon::join( 342 | || install_pool_and_call_range_fn!({ call_range!(2) }), 343 | || install_pool_and_call_range_fn!({ call_range!(3) }), 344 | ) 345 | }, 346 | ) 347 | }; 348 | 349 | return ranged_cts; 350 | } 351 | } 352 | 353 | #[cfg(test)] 354 | mod tests { 355 | use core::time; 356 | use itertools::izip; 357 | use std::{ascii::escape_default, f32::consts::E}; 358 | 359 | use super::*; 360 | use crate::{ 361 | client::gen_pv_exapnd_rtgs, 362 | optimised::{coefficient_u128_to_ciphertext, sub_from_one_precompute}, 363 | plaintext::powers_of_x_modulus, 364 | preprocessing::precompute_indices_pts, 365 | utils::{generate_bfv_parameters, precompute_range_constants}, 366 | }; 367 | use bfv::{BfvParameters, Encoding}; 368 | use rand::thread_rng; 369 | 370 | #[test] 371 | fn range_fn_works() { 372 | let params = generate_bfv_parameters(); 373 | 374 | #[cfg(feature = "level")] 375 | let precompute_level = 10; 376 | 377 | #[cfg(not(feature = "level"))] 378 | let precompute_level = 0; 379 | 380 | let ctx = params.poly_ctx(&PolyType::Q, precompute_level); 381 | 382 | let mut rng = thread_rng(); 383 | let constants = precompute_range_constants(&ctx); 384 | let sub_one_precompute = sub_from_one_precompute(&params, precompute_level); 385 | 386 | let sk = SecretKey::random_with_params(&params, &mut rng); 387 | let mut m = params 388 | .plaintext_modulus_op 389 | .random_vec(params.degree, &mut rng); 390 | 391 | let evaluator = Evaluator::new(params); 392 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 393 | let mut ct = evaluator.encrypt(&sk, &pt, &mut rng); 394 | 395 | evaluator.ciphertext_change_representation(&mut ct, Representation::Evaluation); 396 | 397 | unsafe { evaluator.add_noise(&mut ct, 40) }; 398 | dbg!(evaluator.measure_noise(&sk, &ct)); 399 | 400 | // gen evaluation key 401 | let ek = EvaluationKey::new( 402 | evaluator.params(), 403 | &sk, 404 | &(0..12).into_iter().collect_vec(), 405 | &[], 406 | &[], 407 | &mut rng, 408 | ); 409 | 410 | // limit to single thread 411 | let _ = rayon::ThreadPoolBuilder::new() 412 | .num_threads(8) 413 | .build_global() 414 | .unwrap(); 415 | 416 | let now = std::time::Instant::now(); 417 | let ct_res = range_fn(&ct, &evaluator, &ek, &constants, &sub_one_precompute, &sk); 418 | let time = now.elapsed(); 419 | 420 | println!( 421 | "Time: {:?}, Noise: {}", 422 | time, 423 | evaluator.measure_noise(&sk, &ct_res) 424 | ); 425 | 426 | let res = evaluator.plaintext_decode(&evaluator.decrypt(&sk, &ct_res), Encoding::default()); 427 | 428 | izip!(res.iter(), m.iter()).for_each(|(r, e)| { 429 | if (*e <= 850 || *e >= (65537 - 850)) { 430 | assert_eq!(*r, 1); 431 | } else if *r != 0 { 432 | assert_eq!(*r, 0); 433 | } 434 | }); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use bfv::{ 2 | Ciphertext, CiphertextProto, Encoding, EvaluationKey, EvaluationKeyProto, Evaluator, Plaintext, 3 | Poly, PolyType, Representation, SecretKey, 4 | }; 5 | use itertools::Itertools; 6 | use ndarray::Array2; 7 | use omr::{ 8 | client::{ 9 | construct_lhs, construct_rhs, encrypt_pvw_sk, evaluation_key, pv_decompress, 10 | solve_equations, 11 | }, 12 | level_down, 13 | optimised::sub_from_one_precompute, 14 | preprocessing::{assign_buckets_and_weights, pre_process_batch}, 15 | print_noise, 16 | pvw::*, 17 | server::{ 18 | mul_and_reduce_ranged_cts_to_1, 19 | phase2::{self, phase2_precomputes}, 20 | powers_x::evaluate_powers, 21 | pvw_decrypt::{pvw_decrypt, pvw_decrypt_precomputed, pvw_setup}, 22 | range_fn::{range_fn, range_fn_4_times}, 23 | }, 24 | time_it, 25 | utils::{ 26 | generate_bfv_parameters, generate_random_payloads, precompute_range_constants, 27 | prepare_clues_for_demo, 28 | }, 29 | BUCKET_SIZE, GAMMA, K, 30 | }; 31 | use prost::Message; 32 | use rand::{thread_rng, Rng}; 33 | use traits::TryFromWithParameters; 34 | 35 | // Functions to check runtime of expensive components // 36 | 37 | fn powers_of_x() { 38 | let mut rng = thread_rng(); 39 | let params = generate_bfv_parameters(); 40 | let sk = SecretKey::random_with_params(&params, &mut rng); 41 | let ek = EvaluationKey::new(&params, &sk, &[0], &[], &[], &mut rng); 42 | 43 | let m = params 44 | .plaintext_modulus_op 45 | .random_vec(params.degree, &mut rng); 46 | let evaluator = Evaluator::new(params); 47 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 48 | let ct = evaluator.encrypt(&sk, &pt, &mut rng); 49 | 50 | time_it!("Powers of x [1,256) time: ", 51 | let placeholder = Ciphertext::placeholder(); 52 | let mut calculated = vec![placeholder.clone(); 255]; 53 | calculated[0] = ct; 54 | evaluate_powers(&evaluator, &ek, 2, 4, &mut calculated, false, &sk); 55 | evaluate_powers(&evaluator, &ek, 4, 8, &mut calculated, false, &sk); 56 | evaluate_powers(&evaluator, &ek, 8, 16, &mut calculated, false, &sk); 57 | evaluate_powers(&evaluator, &ek, 16, 32, &mut calculated, false, &sk); 58 | evaluate_powers(&evaluator, &ek, 32, 64, &mut calculated, false, &sk); 59 | evaluate_powers(&evaluator, &ek, 64, 128, &mut calculated, false, &sk); 60 | evaluate_powers(&evaluator, &ek, 128, 256, &mut calculated, false, &sk); 61 | ); 62 | } 63 | 64 | fn call_pvw_decrypt() { 65 | let mut rng = thread_rng(); 66 | let pvw_params = PvwParameters::default(); 67 | let pvw_sk = PvwSecretKey::random(&pvw_params, &mut rng); 68 | let pvw_pk = pvw_sk.public_key(&mut rng); 69 | 70 | let params = generate_bfv_parameters(); 71 | let sk = SecretKey::random_with_params(&params, &mut rng); 72 | 73 | let clue1 = pvw_pk.encrypt(&[0, 0, 0, 0], &mut rng); 74 | let clues = (0..params.degree) 75 | .into_iter() 76 | .map(|_| clue1.clone()) 77 | .collect_vec(); 78 | 79 | let evaluator = Evaluator::new(params); 80 | 81 | let (hint_a, hint_b) = pre_process_batch(&pvw_params, &evaluator, &clues); 82 | 83 | let pvw_sk_cts = encrypt_pvw_sk(&evaluator, &sk, &pvw_sk, &mut rng); 84 | 85 | let ek = EvaluationKey::new(evaluator.params(), &sk, &[0], &[0], &[1], &mut rng); 86 | 87 | time_it!("Pvw Decrypt", 88 | let res = pvw_decrypt( 89 | &pvw_params, 90 | &evaluator, 91 | &hint_a, 92 | &hint_b, 93 | &pvw_sk_cts, 94 | ek.get_rtg_ref(1, 0), 95 | &sk, 96 | ); 97 | ); 98 | 99 | dbg!(evaluator.measure_noise(&sk, &res[0])); 100 | dbg!(evaluator.measure_noise(&sk, &res[1])); 101 | dbg!(evaluator.measure_noise(&sk, &res[2])); 102 | dbg!(evaluator.measure_noise(&sk, &res[3])); 103 | } 104 | 105 | fn call_pvw_decrypt_precomputed() { 106 | let mut rng = thread_rng(); 107 | let pvw_params = PvwParameters::default(); 108 | let pvw_sk = PvwSecretKey::random(&pvw_params, &mut rng); 109 | let pvw_pk = pvw_sk.public_key(&mut rng); 110 | 111 | let params = generate_bfv_parameters(); 112 | let sk = SecretKey::random_with_params(&params, &mut rng); 113 | 114 | let clue1 = pvw_pk.encrypt(&[0, 0, 0, 0], &mut rng); 115 | let clues = (0..params.degree) 116 | .into_iter() 117 | .map(|_| clue1.clone()) 118 | .collect_vec(); 119 | 120 | let evaluator = Evaluator::new(params); 121 | 122 | let (hint_a, hint_b) = pre_process_batch(&pvw_params, &evaluator, &clues); 123 | 124 | let pvw_sk_cts = encrypt_pvw_sk(&evaluator, &sk, &pvw_sk, &mut rng); 125 | 126 | let ek = EvaluationKey::new(evaluator.params(), &sk, &[0], &[0], &[1], &mut rng); 127 | 128 | println!("Running precomputation..."); 129 | let precomputed_pvw_sk_cts = pvw_setup(&evaluator, &ek, &pvw_sk_cts); 130 | 131 | println!("Starting decrypt..."); 132 | time_it!("Pvw Decrypt Precomputed", 133 | let _ = pvw_decrypt_precomputed( 134 | &pvw_params, 135 | &evaluator, 136 | &hint_a, 137 | &hint_b, 138 | &precomputed_pvw_sk_cts, 139 | ek.get_rtg_ref(1, 0), 140 | &sk, 141 | ); 142 | ); 143 | } 144 | 145 | /// Wrapper for setting up necessary things before calling range_fn 146 | fn call_range_fn_once() { 147 | let params = generate_bfv_parameters(); 148 | let level = 0; 149 | let ctx = params.poly_ctx(&PolyType::Q, level); 150 | 151 | let mut rng = thread_rng(); 152 | let constants = precompute_range_constants(&ctx); 153 | let sub_one_precompute = sub_from_one_precompute(&params, level); 154 | 155 | let sk = SecretKey::random_with_params(&params, &mut rng); 156 | let m = params 157 | .plaintext_modulus_op 158 | .random_vec(params.degree, &mut rng); 159 | 160 | let evaluator = Evaluator::new(params); 161 | let pt = evaluator.plaintext_encode(&m, Encoding::default()); 162 | let ct = evaluator.encrypt(&sk, &pt, &mut rng); 163 | 164 | // gen evaluation key 165 | let ek = EvaluationKey::new(evaluator.params(), &sk, &[0], &[], &[], &mut rng); 166 | 167 | time_it!("Range fn time: ", 168 | let _ = range_fn(&ct, &evaluator, &ek, &constants, &sub_one_precompute, &sk); 169 | ); 170 | 171 | let cts = (0..4).into_iter().map(|_| ct.clone()).collect_vec(); 172 | time_it!("Range fn 4 times: ", 173 | let _ = range_fn_4_times(&cts, &evaluator, &ek, &constants, &sub_one_precompute, &sk); 174 | ); 175 | } 176 | 177 | // END // 178 | 179 | fn print_pvw_decrypt_precompute_size() { 180 | let mut rng = thread_rng(); 181 | 182 | let params = generate_bfv_parameters(); 183 | let sk = SecretKey::random_with_params(&params, &mut rng); 184 | let evaluator = Evaluator::new(params); 185 | let pvw_params = PvwParameters::default(); 186 | let pvw_sk = PvwSecretKey::random(&pvw_params, &mut rng); 187 | 188 | let pvw_sk_cts = encrypt_pvw_sk(&evaluator, &sk, &pvw_sk, &mut rng); 189 | 190 | // Don't call `pvw_setup` to get rotations of all `pvw_sk_cts` since it will take sometime. Simply 191 | // calculate no. of checkpoints required per ciphertext in `pvw_sk_ct` for available threads. 192 | 193 | let total_threads = rayon::current_num_threads(); 194 | assert!(total_threads % 4 == 0); 195 | assert!(total_threads >= 8); 196 | // Assign equal no. of threads to each of 4 instances of pvw decrypt 197 | let threads_by_4 = total_threads / 4; 198 | 199 | // Each instance of pvw decrypt performs 512 rotations and plaintext multiplications of single pvw_sk_ct. Since rotations 200 | // are serial, to parallelise we must precompute rotatations of pvw_sk_ct at specific checkpoints (depending on threads_by_4) and store them. 201 | // For ex, when threads_by_4 = 4 it means each call of pvw_decrypt has 4 threads. So we precompute rotations of 202 | // pvw_sk_ct at checkpints that divide 512 into 4 parts (ie 512/threads_by_4) and each part can be processed serially. 203 | // Thus the checkpoints must be 4 instances of pvw_sk_ct rotated by 0, 128, 256, 384. At run time each thread will perform 204 | // `512/4 = 128` rotations. 205 | let instances_of_each_pvw_sk = threads_by_4; 206 | 207 | let total_ciphertexts_stored = instances_of_each_pvw_sk * pvw_sk_cts.len(); 208 | 209 | // Size of each of the 4 `pvw_sk_cts` as well as their `threads_by_4` instances obtained after rotations at checkpoints are equal. 210 | // So we can serialise just one ciphertext and estimate the total size. 211 | let mut ct = pvw_sk_cts[0].clone(); 212 | // Serialisation is not allowed in Evaluation representation since `NTT` backend may differ. This may change in future. 213 | evaluator.ciphertext_change_representation(&mut ct, Representation::Coefficient); 214 | let proto_ct = CiphertextProto::try_from_with_parameters(&ct, evaluator.params()); 215 | let size = proto_ct.encode_to_vec().len(); 216 | 217 | println!( 218 | "Pvw Precompute Size per User: {} bytes", 219 | size * total_ciphertexts_stored 220 | ); 221 | } 222 | 223 | fn print_detection_key_size() { 224 | let mut rng = thread_rng(); 225 | 226 | let params = generate_bfv_parameters(); 227 | 228 | // Client's PVW keys 229 | let pvw_params = PvwParameters::default(); 230 | let pvw_sk = PvwSecretKey::random(&pvw_params, &mut rng); 231 | 232 | // Client's BFV keys 233 | let sk = SecretKey::random_with_params(&params, &mut rng); 234 | 235 | let evaluator = Evaluator::new(params); 236 | 237 | // Client's detection keys 238 | let mut pvw_sk_cts = encrypt_pvw_sk(&evaluator, &sk, &pvw_sk, &mut rng); 239 | let ek = evaluation_key(evaluator.params(), &sk, &mut rng); 240 | 241 | // change representation of pvw_sk_cts from Evaluation to Coefficient due the possibility that we might 242 | // use different NTT backends in future 243 | pvw_sk_cts.iter_mut().for_each(|c| { 244 | evaluator.ciphertext_change_representation(c, Representation::Coefficient); 245 | }); 246 | 247 | let mut bytes = 0; 248 | pvw_sk_cts.iter().for_each(|c| { 249 | let proto = CiphertextProto::try_from_with_parameters(c, evaluator.params()); 250 | bytes += proto.encode_to_vec().len(); 251 | }); 252 | 253 | bytes += EvaluationKeyProto::try_from_with_parameters(&ek, evaluator.params()) 254 | .encode_to_vec() 255 | .len(); 256 | 257 | println!("Detection Key Size: {bytes} bytes"); 258 | } 259 | 260 | fn demo() { 261 | println!("Using {} threads", rayon::current_num_threads()); 262 | if !rayon::current_num_threads().is_power_of_two() { 263 | println!("WARNING: Threads available is not a power of 2. Program isn't capable of fully utilising all cores if total cores are not a power of 2. Moreover, program might panic!"); 264 | } 265 | 266 | let mut rng = thread_rng(); 267 | 268 | let params = generate_bfv_parameters(); 269 | 270 | // Client's PVW keys 271 | let pvw_params = PvwParameters::default(); 272 | let pvw_sk = PvwSecretKey::random(&pvw_params, &mut rng); 273 | let pvw_pk = pvw_sk.public_key(&mut rng); 274 | 275 | // Client's BFV keys 276 | let sk = SecretKey::random_with_params(&params, &mut rng); 277 | 278 | // a random secret key as a placeholder to be passed around in functions. Should be 279 | // removed in production. 280 | let mut random_sk = SecretKey::random_with_params(&params, &mut rng); 281 | 282 | #[cfg(feature = "noise")] 283 | { 284 | // set `random_sk` as `sk` to print correct noise 285 | random_sk = sk.clone(); 286 | } 287 | 288 | let evaluator = Evaluator::new(params); 289 | 290 | // Client's detection keys 291 | let pvw_sk_cts = encrypt_pvw_sk(&evaluator, &sk, &pvw_sk, &mut rng); 292 | let ek = evaluation_key(evaluator.params(), &sk, &mut rng); 293 | 294 | // generate pertinent indices 295 | let pertinency_message_count = 64; 296 | let mut pertinent_indices = vec![]; 297 | while pertinent_indices.len() != pertinency_message_count { 298 | let index = rng.gen_range(0..evaluator.params().degree); 299 | if !pertinent_indices.contains(&index) { 300 | pertinent_indices.push(index); 301 | } 302 | } 303 | pertinent_indices.sort(); 304 | 305 | // Generate clues and payloads 306 | let clues = prepare_clues_for_demo( 307 | &pvw_params, 308 | &pvw_pk, 309 | &pertinent_indices, 310 | evaluator.params().degree, 311 | ); 312 | let payloads = generate_random_payloads(evaluator.params().degree); 313 | 314 | // pre-processing 315 | println!("Phase 1 precomputation..."); 316 | let (precomp_hint_a, precomp_hint_b) = pre_process_batch(&pvw_params, &evaluator, &clues); 317 | 318 | #[cfg(feature = "precomp_pvw")] 319 | let pvw_decrypt_precompute = { 320 | if rayon::current_num_threads() < 8 { 321 | panic!("Feature `precomp_pvw` does not support threads < 8"); 322 | } 323 | pvw_setup(&evaluator, &ek, &pvw_sk_cts) 324 | }; 325 | 326 | #[cfg(feature = "level")] 327 | let range_precompute_level = 10; 328 | 329 | #[cfg(not(feature = "level"))] 330 | let range_precompute_level = 0; 331 | 332 | let precomp_range_constants = precompute_range_constants( 333 | &evaluator 334 | .params() 335 | .poly_ctx(&PolyType::Q, range_precompute_level), 336 | ); 337 | let precomp_sub_from_one = sub_from_one_precompute(evaluator.params(), range_precompute_level); 338 | 339 | // Running phase 1 340 | println!("Phase 1..."); 341 | let p1now = std::time::SystemTime::now(); 342 | let mut phase1_ciphertext = phase1( 343 | &evaluator, 344 | &pvw_params, 345 | &precomp_hint_a, 346 | &precomp_hint_b, 347 | &precomp_range_constants, 348 | &precomp_sub_from_one, 349 | &ek, 350 | &random_sk, 351 | #[cfg(not(feature = "precomp_pvw"))] 352 | &pvw_sk_cts, 353 | #[cfg(feature = "precomp_pvw")] 354 | &pvw_decrypt_precompute, 355 | ); 356 | let p1_time_ms = p1now.elapsed().unwrap().as_millis(); 357 | println!("Phase 1 duration: {} ms", p1_time_ms); 358 | 359 | // phase1 mods down to level 12 in the end irrespective of whether level feature is enabled. Otherwise, phase2 will be very expensive. 360 | let level = 12; 361 | 362 | // Precomp phase 2 363 | let (_, buckets, weights) = assign_buckets_and_weights( 364 | K * 2, 365 | GAMMA, 366 | evaluator.params().plaintext_modulus, 367 | evaluator.params().degree, 368 | &mut rng, 369 | ); 370 | 371 | let (pts_32_batch, pts_4_roll, pts_1_roll) = 372 | phase2_precomputes(&evaluator, evaluator.params().degree, level); 373 | 374 | // Phase 2 375 | println!("Phase 2..."); 376 | let p2now = std::time::SystemTime::now(); 377 | let (indices_ct, weights_ct) = phase2( 378 | &evaluator, 379 | &mut phase1_ciphertext, 380 | &ek, 381 | &buckets, 382 | &weights, 383 | &payloads, 384 | &pts_32_batch, 385 | &pts_4_roll, 386 | &pts_1_roll, 387 | level, 388 | &random_sk, 389 | ); 390 | let p2_time_ms = p2now.elapsed().unwrap().as_millis(); 391 | println!("Phase 2 duration: {} ms", p2_time_ms); 392 | 393 | println!("Total server duration: {} ms", p2_time_ms + p1_time_ms); 394 | 395 | // Client 396 | time_it!("Client", 397 | let messages = client_processing( 398 | &evaluator, 399 | &indices_ct, 400 | &weights_ct, 401 | &sk, 402 | &buckets, 403 | &weights, 404 | ); 405 | ); 406 | 407 | // Check correctness 408 | let mut expected_messages = vec![]; 409 | pertinent_indices.iter().for_each(|i| { 410 | expected_messages.push(payloads[*i].clone()); 411 | }); 412 | 413 | assert_eq!(expected_messages, messages); 414 | } 415 | 416 | fn phase1( 417 | evaluator: &Evaluator, 418 | pvw_params: &PvwParameters, 419 | precomp_hint_a: &[Plaintext], 420 | precomp_hint_b: &[Plaintext], 421 | precomp_range_constants: &Array2<u64>, 422 | precomp_sub_from_one: &[u64], 423 | ek: &EvaluationKey, 424 | sk: &SecretKey, 425 | #[cfg(feature = "precomp_pvw")] precomputed_pvw_sk_cts: &[Vec<Ciphertext>], 426 | #[cfg(not(feature = "precomp_pvw"))] pvw_sk_cts: &[Ciphertext], 427 | ) -> Ciphertext { 428 | #[cfg(feature = "precomp_pvw")] 429 | let decrypted_cts = { 430 | pvw_decrypt_precomputed( 431 | &pvw_params, 432 | &evaluator, 433 | &precomp_hint_a, 434 | &precomp_hint_b, 435 | &precomputed_pvw_sk_cts, 436 | ek.get_rtg_ref(1, 0), 437 | &sk, 438 | ) 439 | }; 440 | 441 | #[cfg(not(feature = "precomp_pvw"))] 442 | let decrypted_cts = pvw_decrypt( 443 | &pvw_params, 444 | &evaluator, 445 | precomp_hint_a, 446 | precomp_hint_b, 447 | &pvw_sk_cts, 448 | ek.get_rtg_ref(1, 0), 449 | &sk, 450 | ); 451 | 452 | print_noise!( 453 | decrypted_cts.iter().enumerate().for_each(|(index, ct)| { 454 | println!( 455 | "Decrypted Ct {index} noise: {}", 456 | evaluator.measure_noise(&sk, ct) 457 | ); 458 | }); 459 | ); 460 | 461 | // ranged cts are in coefficient form 462 | let ranged_cts = range_fn_4_times( 463 | &decrypted_cts, 464 | &evaluator, 465 | &ek, 466 | precomp_range_constants, 467 | precomp_sub_from_one, 468 | &sk, 469 | ); 470 | 471 | print_noise!( 472 | println!( 473 | "Ranged cts noise: {} {} {} {}", 474 | evaluator.measure_noise(sk, &ranged_cts.0 .0), 475 | evaluator.measure_noise(sk, &ranged_cts.0 .1), 476 | evaluator.measure_noise(sk, &ranged_cts.1 .0), 477 | evaluator.measure_noise(sk, &ranged_cts.1 .1) 478 | ); 479 | ); 480 | 481 | let mut v = mul_and_reduce_ranged_cts_to_1(&ranged_cts, &evaluator, &ek, &sk); 482 | 483 | print_noise!( 484 | println!( 485 | "Phase 1 end noise (before mod down): {}", 486 | evaluator.measure_noise(sk, &v) 487 | ); 488 | ); 489 | 490 | // mod down to level 12 irrespective of whether `level` feature is enabled or not. Otherwise 491 | // phase 2 will be very expensive 492 | evaluator.mod_down_level(&mut v, 12); 493 | 494 | print_noise!( 495 | println!( 496 | "Phase 1 end noise (after mod down): {}", 497 | evaluator.measure_noise(sk, &v) 498 | ); 499 | ); 500 | 501 | v 502 | } 503 | 504 | fn phase2( 505 | evaluator: &Evaluator, 506 | pv: &mut Ciphertext, 507 | ek: &EvaluationKey, 508 | buckets: &[Vec<u64>], 509 | weights: &[Vec<u64>], 510 | payloads: &[Vec<u64>], 511 | pts_32_batch: &[Plaintext], 512 | pts_4_roll: &[Plaintext], 513 | pts_1_roll: &[Plaintext], 514 | level: usize, 515 | sk: &SecretKey, 516 | ) -> (Ciphertext, Ciphertext) { 517 | // switch to Evaluation representation for efficient rotations and scalar multiplications 518 | evaluator.ciphertext_change_representation(pv, Representation::Evaluation); 519 | 520 | let (mut indices_ct, mut weights_ct) = phase2::phase2( 521 | evaluator, 522 | pv, 523 | &ek, 524 | level, 525 | &pts_32_batch, 526 | &pts_4_roll, 527 | &pts_1_roll, 528 | &payloads, 529 | &buckets, 530 | &weights, 531 | sk, 532 | ); 533 | 534 | print_noise!( 535 | println!( 536 | "Phase 2 end (before mod down noise) - indices_ct:{}, weights_ct:{} ", 537 | evaluator.measure_noise(&sk, &indices_ct), 538 | evaluator.measure_noise(&sk, &weights_ct) 539 | ); 540 | ); 541 | 542 | level_down!( 543 | // mod down to last level 544 | evaluator.mod_down_level(&mut indices_ct, 14); 545 | evaluator.mod_down_level(&mut weights_ct, 14); 546 | ); 547 | 548 | print_noise!( 549 | println!( 550 | "Phase 2 end (after mod down noise) - indices_ct:{}, weights_ct:{} ", 551 | evaluator.measure_noise(&sk, &indices_ct), 552 | evaluator.measure_noise(&sk, &weights_ct) 553 | ); 554 | ); 555 | 556 | (indices_ct, weights_ct) 557 | } 558 | 559 | fn client_processing( 560 | evaluator: &Evaluator, 561 | indices_ct: &Ciphertext, 562 | weights_ct: &Ciphertext, 563 | sk: &SecretKey, 564 | buckets: &[Vec<u64>], 565 | weights: &[Vec<u64>], 566 | ) -> Vec<Vec<u64>> { 567 | print_noise!( 568 | println!( 569 | "Client's indices_ct noise : {}", 570 | evaluator.measure_noise(&sk, &indices_ct) 571 | ); 572 | println!( 573 | "Client's weights_ct noise : {}", 574 | evaluator.measure_noise(&sk, &weights_ct) 575 | ); 576 | ); 577 | 578 | // construct lhs 579 | let pv = pv_decompress(evaluator, indices_ct, sk); 580 | let lhs = construct_lhs(&pv, buckets, weights, K, GAMMA, evaluator.params().degree); 581 | 582 | // decrypt weights_ct to construct rhs 583 | let weights_vec = 584 | evaluator.plaintext_decode(&evaluator.decrypt(sk, weights_ct), Encoding::default()); 585 | let rhs = construct_rhs(&weights_vec, BUCKET_SIZE); 586 | 587 | let messages = solve_equations(lhs, rhs, K, evaluator.params().plaintext_modulus); 588 | messages 589 | } 590 | 591 | fn print_menu() { 592 | let menu = " 593 | Select one of the options 594 | (1) Runs demo 595 | (2) Print detection key size 596 | (3) Print storage required per user for `pvw_precompute` 597 | 598 | For more info, please refer to README.md 599 | "; 600 | 601 | println!("{menu}"); 602 | } 603 | 604 | fn main() { 605 | let option = std::env::args().nth(1); 606 | let threads = std::env::args().nth(2); 607 | 608 | let option = option.map_or(0, |o| o.parse::<usize>().map_or(0, |o| o)); 609 | 610 | if threads.is_some() { 611 | let threads = threads 612 | .unwrap() 613 | .parse::<usize>() 614 | .expect("Threads must be a positive integer"); 615 | rayon::ThreadPoolBuilder::new() 616 | .num_threads(threads) 617 | .build_global() 618 | .unwrap() 619 | } 620 | 621 | if option == 0 { 622 | print_menu(); 623 | } else { 624 | if option == 1 { 625 | demo(); 626 | } else if option == 2 { 627 | print_detection_key_size(); 628 | } else if option == 3 { 629 | print_pvw_decrypt_precompute_size(); 630 | } 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "aligned-vec" 16 | version = "0.5.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" 19 | 20 | [[package]] 21 | name = "anes" 22 | version = "0.1.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.72" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" 31 | 32 | [[package]] 33 | name = "approx" 34 | version = "0.5.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 37 | dependencies = [ 38 | "num-traits", 39 | ] 40 | 41 | [[package]] 42 | name = "atty" 43 | version = "0.2.14" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 46 | dependencies = [ 47 | "hermit-abi 0.1.19", 48 | "libc", 49 | "winapi", 50 | ] 51 | 52 | [[package]] 53 | name = "autocfg" 54 | version = "1.1.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 57 | 58 | [[package]] 59 | name = "bfv" 60 | version = "0.1.0" 61 | source = "git+https://github.com/Janmajayamall/bfv.git?branch=main#3de3c56b440b38e5035fc6ca6afb7c18cb0429b8" 62 | dependencies = [ 63 | "concrete-ntt", 64 | "crypto-bigint", 65 | "hexl-rs", 66 | "itertools", 67 | "ndarray", 68 | "num-bigint", 69 | "num-bigint-dig", 70 | "num-traits", 71 | "prost", 72 | "prost-build", 73 | "rand", 74 | "rand_chacha", 75 | "seq-macro", 76 | "traits", 77 | ] 78 | 79 | [[package]] 80 | name = "bindgen" 81 | version = "0.63.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" 84 | dependencies = [ 85 | "bitflags 1.3.2", 86 | "cexpr", 87 | "clang-sys", 88 | "lazy_static", 89 | "lazycell", 90 | "log", 91 | "peeking_take_while", 92 | "proc-macro2", 93 | "quote", 94 | "regex", 95 | "rustc-hash", 96 | "shlex", 97 | "syn 1.0.109", 98 | "which", 99 | ] 100 | 101 | [[package]] 102 | name = "bitflags" 103 | version = "1.3.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "2.3.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" 112 | 113 | [[package]] 114 | name = "bumpalo" 115 | version = "3.13.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 118 | 119 | [[package]] 120 | name = "bytemuck" 121 | version = "1.13.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" 124 | 125 | [[package]] 126 | name = "byteorder" 127 | version = "1.4.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 130 | 131 | [[package]] 132 | name = "bytes" 133 | version = "1.4.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 136 | 137 | [[package]] 138 | name = "cast" 139 | version = "0.3.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 142 | 143 | [[package]] 144 | name = "cc" 145 | version = "1.0.82" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" 148 | dependencies = [ 149 | "libc", 150 | ] 151 | 152 | [[package]] 153 | name = "cexpr" 154 | version = "0.6.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 157 | dependencies = [ 158 | "nom", 159 | ] 160 | 161 | [[package]] 162 | name = "cfg-if" 163 | version = "1.0.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 166 | 167 | [[package]] 168 | name = "ciborium" 169 | version = "0.2.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" 172 | dependencies = [ 173 | "ciborium-io", 174 | "ciborium-ll", 175 | "serde", 176 | ] 177 | 178 | [[package]] 179 | name = "ciborium-io" 180 | version = "0.2.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" 183 | 184 | [[package]] 185 | name = "ciborium-ll" 186 | version = "0.2.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" 189 | dependencies = [ 190 | "ciborium-io", 191 | "half", 192 | ] 193 | 194 | [[package]] 195 | name = "clang-sys" 196 | version = "1.6.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" 199 | dependencies = [ 200 | "glob", 201 | "libc", 202 | "libloading", 203 | ] 204 | 205 | [[package]] 206 | name = "clap" 207 | version = "3.2.25" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 210 | dependencies = [ 211 | "bitflags 1.3.2", 212 | "clap_lex", 213 | "indexmap", 214 | "textwrap", 215 | ] 216 | 217 | [[package]] 218 | name = "clap_lex" 219 | version = "0.2.4" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 222 | dependencies = [ 223 | "os_str_bytes", 224 | ] 225 | 226 | [[package]] 227 | name = "cmake" 228 | version = "0.1.50" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 231 | dependencies = [ 232 | "cc", 233 | ] 234 | 235 | [[package]] 236 | name = "concrete-ntt" 237 | version = "0.1.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "aeae2d782381ec4170379f7e7527cb9999e5528166f33f4ab650aafa6eeb62fb" 240 | dependencies = [ 241 | "aligned-vec", 242 | "pulp", 243 | ] 244 | 245 | [[package]] 246 | name = "criterion" 247 | version = "0.4.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 250 | dependencies = [ 251 | "anes", 252 | "atty", 253 | "cast", 254 | "ciborium", 255 | "clap", 256 | "criterion-plot", 257 | "itertools", 258 | "lazy_static", 259 | "num-traits", 260 | "oorandom", 261 | "plotters", 262 | "rayon", 263 | "regex", 264 | "serde", 265 | "serde_derive", 266 | "serde_json", 267 | "tinytemplate", 268 | "walkdir", 269 | ] 270 | 271 | [[package]] 272 | name = "criterion-plot" 273 | version = "0.5.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 276 | dependencies = [ 277 | "cast", 278 | "itertools", 279 | ] 280 | 281 | [[package]] 282 | name = "crossbeam-channel" 283 | version = "0.5.8" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 286 | dependencies = [ 287 | "cfg-if", 288 | "crossbeam-utils", 289 | ] 290 | 291 | [[package]] 292 | name = "crossbeam-deque" 293 | version = "0.8.3" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 296 | dependencies = [ 297 | "cfg-if", 298 | "crossbeam-epoch", 299 | "crossbeam-utils", 300 | ] 301 | 302 | [[package]] 303 | name = "crossbeam-epoch" 304 | version = "0.9.15" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 307 | dependencies = [ 308 | "autocfg", 309 | "cfg-if", 310 | "crossbeam-utils", 311 | "memoffset", 312 | "scopeguard", 313 | ] 314 | 315 | [[package]] 316 | name = "crossbeam-utils" 317 | version = "0.8.16" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 320 | dependencies = [ 321 | "cfg-if", 322 | ] 323 | 324 | [[package]] 325 | name = "crypto-bigint" 326 | version = "0.4.9" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" 329 | dependencies = [ 330 | "rand_core", 331 | "subtle", 332 | ] 333 | 334 | [[package]] 335 | name = "cty" 336 | version = "0.2.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 339 | 340 | [[package]] 341 | name = "either" 342 | version = "1.9.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 345 | 346 | [[package]] 347 | name = "errno" 348 | version = "0.3.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" 351 | dependencies = [ 352 | "errno-dragonfly", 353 | "libc", 354 | "windows-sys", 355 | ] 356 | 357 | [[package]] 358 | name = "errno-dragonfly" 359 | version = "0.1.2" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 362 | dependencies = [ 363 | "cc", 364 | "libc", 365 | ] 366 | 367 | [[package]] 368 | name = "fastrand" 369 | version = "2.0.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" 372 | 373 | [[package]] 374 | name = "fixedbitset" 375 | version = "0.4.2" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 378 | 379 | [[package]] 380 | name = "getrandom" 381 | version = "0.2.10" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 384 | dependencies = [ 385 | "cfg-if", 386 | "libc", 387 | "wasi", 388 | ] 389 | 390 | [[package]] 391 | name = "glob" 392 | version = "0.3.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 395 | 396 | [[package]] 397 | name = "half" 398 | version = "1.8.2" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 401 | 402 | [[package]] 403 | name = "hashbrown" 404 | version = "0.12.3" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 407 | 408 | [[package]] 409 | name = "heck" 410 | version = "0.4.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 413 | 414 | [[package]] 415 | name = "hermit-abi" 416 | version = "0.1.19" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 419 | dependencies = [ 420 | "libc", 421 | ] 422 | 423 | [[package]] 424 | name = "hermit-abi" 425 | version = "0.3.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 428 | 429 | [[package]] 430 | name = "hexl-rs" 431 | version = "0.1.0" 432 | source = "git+https://github.com/Janmajayamall/hexl-rs.git#c2d23d6ab458b388bef337b2ed8526b07f42f081" 433 | dependencies = [ 434 | "bindgen", 435 | "cmake", 436 | "link-cplusplus", 437 | "ndarray", 438 | "rand", 439 | ] 440 | 441 | [[package]] 442 | name = "indexmap" 443 | version = "1.9.3" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 446 | dependencies = [ 447 | "autocfg", 448 | "hashbrown", 449 | ] 450 | 451 | [[package]] 452 | name = "itertools" 453 | version = "0.10.5" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 456 | dependencies = [ 457 | "either", 458 | ] 459 | 460 | [[package]] 461 | name = "itoa" 462 | version = "1.0.9" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 465 | 466 | [[package]] 467 | name = "js-sys" 468 | version = "0.3.64" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 471 | dependencies = [ 472 | "wasm-bindgen", 473 | ] 474 | 475 | [[package]] 476 | name = "lazy_static" 477 | version = "1.4.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 480 | dependencies = [ 481 | "spin", 482 | ] 483 | 484 | [[package]] 485 | name = "lazycell" 486 | version = "1.3.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 489 | 490 | [[package]] 491 | name = "libc" 492 | version = "0.2.147" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 495 | 496 | [[package]] 497 | name = "libloading" 498 | version = "0.7.4" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 501 | dependencies = [ 502 | "cfg-if", 503 | "winapi", 504 | ] 505 | 506 | [[package]] 507 | name = "libm" 508 | version = "0.2.7" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" 511 | 512 | [[package]] 513 | name = "link-cplusplus" 514 | version = "1.0.9" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" 517 | dependencies = [ 518 | "cc", 519 | ] 520 | 521 | [[package]] 522 | name = "linux-raw-sys" 523 | version = "0.4.5" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" 526 | 527 | [[package]] 528 | name = "log" 529 | version = "0.4.19" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 532 | 533 | [[package]] 534 | name = "matrixmultiply" 535 | version = "0.3.7" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" 538 | dependencies = [ 539 | "autocfg", 540 | "rawpointer", 541 | ] 542 | 543 | [[package]] 544 | name = "memchr" 545 | version = "2.5.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 548 | 549 | [[package]] 550 | name = "memoffset" 551 | version = "0.9.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 554 | dependencies = [ 555 | "autocfg", 556 | ] 557 | 558 | [[package]] 559 | name = "mimalloc-rust" 560 | version = "0.2.1" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "5eb726c8298efb4010b2c46d8050e4be36cf807b9d9e98cb112f830914fc9bbe" 563 | dependencies = [ 564 | "cty", 565 | "mimalloc-rust-sys", 566 | ] 567 | 568 | [[package]] 569 | name = "mimalloc-rust-sys" 570 | version = "1.7.9-source" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "6413e13241a9809f291568133eca6694572cf528c1a6175502d090adce5dd5db" 573 | dependencies = [ 574 | "cc", 575 | "cty", 576 | ] 577 | 578 | [[package]] 579 | name = "minimal-lexical" 580 | version = "0.2.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 583 | 584 | [[package]] 585 | name = "multimap" 586 | version = "0.8.3" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 589 | 590 | [[package]] 591 | name = "nalgebra" 592 | version = "0.29.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" 595 | dependencies = [ 596 | "approx", 597 | "matrixmultiply", 598 | "nalgebra-macros", 599 | "num-complex", 600 | "num-rational", 601 | "num-traits", 602 | "rand", 603 | "rand_distr", 604 | "simba", 605 | "typenum", 606 | ] 607 | 608 | [[package]] 609 | name = "nalgebra-macros" 610 | version = "0.1.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" 613 | dependencies = [ 614 | "proc-macro2", 615 | "quote", 616 | "syn 1.0.109", 617 | ] 618 | 619 | [[package]] 620 | name = "ndarray" 621 | version = "0.15.6" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" 624 | dependencies = [ 625 | "matrixmultiply", 626 | "num-complex", 627 | "num-integer", 628 | "num-traits", 629 | "rawpointer", 630 | "rayon", 631 | ] 632 | 633 | [[package]] 634 | name = "nom" 635 | version = "7.1.3" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 638 | dependencies = [ 639 | "memchr", 640 | "minimal-lexical", 641 | ] 642 | 643 | [[package]] 644 | name = "num-bigint" 645 | version = "0.4.3" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 648 | dependencies = [ 649 | "autocfg", 650 | "num-integer", 651 | "num-traits", 652 | "rand", 653 | ] 654 | 655 | [[package]] 656 | name = "num-bigint-dig" 657 | version = "0.8.4" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 660 | dependencies = [ 661 | "byteorder", 662 | "lazy_static", 663 | "libm", 664 | "num-integer", 665 | "num-iter", 666 | "num-traits", 667 | "rand", 668 | "serde", 669 | "smallvec", 670 | ] 671 | 672 | [[package]] 673 | name = "num-complex" 674 | version = "0.4.3" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" 677 | dependencies = [ 678 | "num-traits", 679 | ] 680 | 681 | [[package]] 682 | name = "num-integer" 683 | version = "0.1.45" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 686 | dependencies = [ 687 | "autocfg", 688 | "num-traits", 689 | ] 690 | 691 | [[package]] 692 | name = "num-iter" 693 | version = "0.1.43" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 696 | dependencies = [ 697 | "autocfg", 698 | "num-integer", 699 | "num-traits", 700 | ] 701 | 702 | [[package]] 703 | name = "num-rational" 704 | version = "0.4.1" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 707 | dependencies = [ 708 | "autocfg", 709 | "num-integer", 710 | "num-traits", 711 | ] 712 | 713 | [[package]] 714 | name = "num-traits" 715 | version = "0.2.16" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 718 | dependencies = [ 719 | "autocfg", 720 | "libm", 721 | ] 722 | 723 | [[package]] 724 | name = "num_cpus" 725 | version = "1.16.0" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 728 | dependencies = [ 729 | "hermit-abi 0.3.2", 730 | "libc", 731 | ] 732 | 733 | [[package]] 734 | name = "omr" 735 | version = "0.1.0" 736 | dependencies = [ 737 | "bfv", 738 | "byteorder", 739 | "bytes", 740 | "criterion", 741 | "hexl-rs", 742 | "itertools", 743 | "mimalloc-rust", 744 | "ndarray", 745 | "num-bigint-dig", 746 | "num-traits", 747 | "prost", 748 | "prost-build", 749 | "rand", 750 | "rand_chacha", 751 | "rayon", 752 | "statrs", 753 | "traits", 754 | "walkdir", 755 | ] 756 | 757 | [[package]] 758 | name = "once_cell" 759 | version = "1.18.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 762 | 763 | [[package]] 764 | name = "oorandom" 765 | version = "11.1.3" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 768 | 769 | [[package]] 770 | name = "os_str_bytes" 771 | version = "6.5.1" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" 774 | 775 | [[package]] 776 | name = "paste" 777 | version = "1.0.14" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 780 | 781 | [[package]] 782 | name = "peeking_take_while" 783 | version = "0.1.2" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 786 | 787 | [[package]] 788 | name = "petgraph" 789 | version = "0.6.3" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" 792 | dependencies = [ 793 | "fixedbitset", 794 | "indexmap", 795 | ] 796 | 797 | [[package]] 798 | name = "plotters" 799 | version = "0.3.5" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" 802 | dependencies = [ 803 | "num-traits", 804 | "plotters-backend", 805 | "plotters-svg", 806 | "wasm-bindgen", 807 | "web-sys", 808 | ] 809 | 810 | [[package]] 811 | name = "plotters-backend" 812 | version = "0.3.5" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" 815 | 816 | [[package]] 817 | name = "plotters-svg" 818 | version = "0.3.5" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" 821 | dependencies = [ 822 | "plotters-backend", 823 | ] 824 | 825 | [[package]] 826 | name = "ppv-lite86" 827 | version = "0.2.17" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 830 | 831 | [[package]] 832 | name = "prettyplease" 833 | version = "0.1.25" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" 836 | dependencies = [ 837 | "proc-macro2", 838 | "syn 1.0.109", 839 | ] 840 | 841 | [[package]] 842 | name = "proc-macro2" 843 | version = "1.0.66" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 846 | dependencies = [ 847 | "unicode-ident", 848 | ] 849 | 850 | [[package]] 851 | name = "prost" 852 | version = "0.11.9" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" 855 | dependencies = [ 856 | "bytes", 857 | "prost-derive", 858 | ] 859 | 860 | [[package]] 861 | name = "prost-build" 862 | version = "0.11.9" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" 865 | dependencies = [ 866 | "bytes", 867 | "heck", 868 | "itertools", 869 | "lazy_static", 870 | "log", 871 | "multimap", 872 | "petgraph", 873 | "prettyplease", 874 | "prost", 875 | "prost-types", 876 | "regex", 877 | "syn 1.0.109", 878 | "tempfile", 879 | "which", 880 | ] 881 | 882 | [[package]] 883 | name = "prost-derive" 884 | version = "0.11.9" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" 887 | dependencies = [ 888 | "anyhow", 889 | "itertools", 890 | "proc-macro2", 891 | "quote", 892 | "syn 1.0.109", 893 | ] 894 | 895 | [[package]] 896 | name = "prost-types" 897 | version = "0.11.9" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" 900 | dependencies = [ 901 | "prost", 902 | ] 903 | 904 | [[package]] 905 | name = "pulp" 906 | version = "0.11.11" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "866e8018d6397b0717100dd4a7948fc8cbc8c4b8ce3e39e98a0e1e878d3ba925" 909 | dependencies = [ 910 | "bytemuck", 911 | ] 912 | 913 | [[package]] 914 | name = "quote" 915 | version = "1.0.32" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" 918 | dependencies = [ 919 | "proc-macro2", 920 | ] 921 | 922 | [[package]] 923 | name = "rand" 924 | version = "0.8.5" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 927 | dependencies = [ 928 | "libc", 929 | "rand_chacha", 930 | "rand_core", 931 | ] 932 | 933 | [[package]] 934 | name = "rand_chacha" 935 | version = "0.3.1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 938 | dependencies = [ 939 | "ppv-lite86", 940 | "rand_core", 941 | ] 942 | 943 | [[package]] 944 | name = "rand_core" 945 | version = "0.6.4" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 948 | dependencies = [ 949 | "getrandom", 950 | ] 951 | 952 | [[package]] 953 | name = "rand_distr" 954 | version = "0.4.3" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" 957 | dependencies = [ 958 | "num-traits", 959 | "rand", 960 | ] 961 | 962 | [[package]] 963 | name = "rawpointer" 964 | version = "0.2.1" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 967 | 968 | [[package]] 969 | name = "rayon" 970 | version = "1.7.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 973 | dependencies = [ 974 | "either", 975 | "rayon-core", 976 | ] 977 | 978 | [[package]] 979 | name = "rayon-core" 980 | version = "1.11.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 983 | dependencies = [ 984 | "crossbeam-channel", 985 | "crossbeam-deque", 986 | "crossbeam-utils", 987 | "num_cpus", 988 | ] 989 | 990 | [[package]] 991 | name = "redox_syscall" 992 | version = "0.3.5" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 995 | dependencies = [ 996 | "bitflags 1.3.2", 997 | ] 998 | 999 | [[package]] 1000 | name = "regex" 1001 | version = "1.9.3" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 1004 | dependencies = [ 1005 | "aho-corasick", 1006 | "memchr", 1007 | "regex-automata", 1008 | "regex-syntax", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "regex-automata" 1013 | version = "0.3.6" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 1016 | dependencies = [ 1017 | "aho-corasick", 1018 | "memchr", 1019 | "regex-syntax", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "regex-syntax" 1024 | version = "0.7.4" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 1027 | 1028 | [[package]] 1029 | name = "rustc-hash" 1030 | version = "1.1.0" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1033 | 1034 | [[package]] 1035 | name = "rustix" 1036 | version = "0.38.7" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" 1039 | dependencies = [ 1040 | "bitflags 2.3.3", 1041 | "errno", 1042 | "libc", 1043 | "linux-raw-sys", 1044 | "windows-sys", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "ryu" 1049 | version = "1.0.15" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 1052 | 1053 | [[package]] 1054 | name = "safe_arch" 1055 | version = "0.7.1" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" 1058 | dependencies = [ 1059 | "bytemuck", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "same-file" 1064 | version = "1.0.6" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1067 | dependencies = [ 1068 | "winapi-util", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "scopeguard" 1073 | version = "1.2.0" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1076 | 1077 | [[package]] 1078 | name = "seq-macro" 1079 | version = "0.3.5" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" 1082 | 1083 | [[package]] 1084 | name = "serde" 1085 | version = "1.0.183" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" 1088 | dependencies = [ 1089 | "serde_derive", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "serde_derive" 1094 | version = "1.0.183" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" 1097 | dependencies = [ 1098 | "proc-macro2", 1099 | "quote", 1100 | "syn 2.0.28", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "serde_json" 1105 | version = "1.0.104" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" 1108 | dependencies = [ 1109 | "itoa", 1110 | "ryu", 1111 | "serde", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "shlex" 1116 | version = "1.1.0" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 1119 | 1120 | [[package]] 1121 | name = "simba" 1122 | version = "0.6.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" 1125 | dependencies = [ 1126 | "approx", 1127 | "num-complex", 1128 | "num-traits", 1129 | "paste", 1130 | "wide", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "smallvec" 1135 | version = "1.11.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 1138 | 1139 | [[package]] 1140 | name = "spin" 1141 | version = "0.5.2" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1144 | 1145 | [[package]] 1146 | name = "statrs" 1147 | version = "0.16.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "2d08e5e1748192713cc281da8b16924fb46be7b0c2431854eadc785823e5696e" 1150 | dependencies = [ 1151 | "approx", 1152 | "lazy_static", 1153 | "nalgebra", 1154 | "num-traits", 1155 | "rand", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "subtle" 1160 | version = "2.5.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1163 | 1164 | [[package]] 1165 | name = "syn" 1166 | version = "1.0.109" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1169 | dependencies = [ 1170 | "proc-macro2", 1171 | "quote", 1172 | "unicode-ident", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "syn" 1177 | version = "2.0.28" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" 1180 | dependencies = [ 1181 | "proc-macro2", 1182 | "quote", 1183 | "unicode-ident", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "tempfile" 1188 | version = "3.7.1" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" 1191 | dependencies = [ 1192 | "cfg-if", 1193 | "fastrand", 1194 | "redox_syscall", 1195 | "rustix", 1196 | "windows-sys", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "textwrap" 1201 | version = "0.16.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 1204 | 1205 | [[package]] 1206 | name = "tinytemplate" 1207 | version = "1.2.1" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 1210 | dependencies = [ 1211 | "serde", 1212 | "serde_json", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "traits" 1217 | version = "0.1.0" 1218 | source = "git+https://github.com/Janmajayamall/bfv.git?branch=main#3de3c56b440b38e5035fc6ca6afb7c18cb0429b8" 1219 | 1220 | [[package]] 1221 | name = "typenum" 1222 | version = "1.16.0" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1225 | 1226 | [[package]] 1227 | name = "unicode-ident" 1228 | version = "1.0.11" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1231 | 1232 | [[package]] 1233 | name = "walkdir" 1234 | version = "2.3.3" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 1237 | dependencies = [ 1238 | "same-file", 1239 | "winapi-util", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "wasi" 1244 | version = "0.11.0+wasi-snapshot-preview1" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1247 | 1248 | [[package]] 1249 | name = "wasm-bindgen" 1250 | version = "0.2.87" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1253 | dependencies = [ 1254 | "cfg-if", 1255 | "wasm-bindgen-macro", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "wasm-bindgen-backend" 1260 | version = "0.2.87" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1263 | dependencies = [ 1264 | "bumpalo", 1265 | "log", 1266 | "once_cell", 1267 | "proc-macro2", 1268 | "quote", 1269 | "syn 2.0.28", 1270 | "wasm-bindgen-shared", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "wasm-bindgen-macro" 1275 | version = "0.2.87" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 1278 | dependencies = [ 1279 | "quote", 1280 | "wasm-bindgen-macro-support", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "wasm-bindgen-macro-support" 1285 | version = "0.2.87" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 1288 | dependencies = [ 1289 | "proc-macro2", 1290 | "quote", 1291 | "syn 2.0.28", 1292 | "wasm-bindgen-backend", 1293 | "wasm-bindgen-shared", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "wasm-bindgen-shared" 1298 | version = "0.2.87" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 1301 | 1302 | [[package]] 1303 | name = "web-sys" 1304 | version = "0.3.64" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" 1307 | dependencies = [ 1308 | "js-sys", 1309 | "wasm-bindgen", 1310 | ] 1311 | 1312 | [[package]] 1313 | name = "which" 1314 | version = "4.4.0" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" 1317 | dependencies = [ 1318 | "either", 1319 | "libc", 1320 | "once_cell", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "wide" 1325 | version = "0.7.11" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" 1328 | dependencies = [ 1329 | "bytemuck", 1330 | "safe_arch", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "winapi" 1335 | version = "0.3.9" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1338 | dependencies = [ 1339 | "winapi-i686-pc-windows-gnu", 1340 | "winapi-x86_64-pc-windows-gnu", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "winapi-i686-pc-windows-gnu" 1345 | version = "0.4.0" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1348 | 1349 | [[package]] 1350 | name = "winapi-util" 1351 | version = "0.1.5" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1354 | dependencies = [ 1355 | "winapi", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "winapi-x86_64-pc-windows-gnu" 1360 | version = "0.4.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1363 | 1364 | [[package]] 1365 | name = "windows-sys" 1366 | version = "0.48.0" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1369 | dependencies = [ 1370 | "windows-targets", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "windows-targets" 1375 | version = "0.48.1" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 1378 | dependencies = [ 1379 | "windows_aarch64_gnullvm", 1380 | "windows_aarch64_msvc", 1381 | "windows_i686_gnu", 1382 | "windows_i686_msvc", 1383 | "windows_x86_64_gnu", 1384 | "windows_x86_64_gnullvm", 1385 | "windows_x86_64_msvc", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "windows_aarch64_gnullvm" 1390 | version = "0.48.0" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1393 | 1394 | [[package]] 1395 | name = "windows_aarch64_msvc" 1396 | version = "0.48.0" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1399 | 1400 | [[package]] 1401 | name = "windows_i686_gnu" 1402 | version = "0.48.0" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1405 | 1406 | [[package]] 1407 | name = "windows_i686_msvc" 1408 | version = "0.48.0" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1411 | 1412 | [[package]] 1413 | name = "windows_x86_64_gnu" 1414 | version = "0.48.0" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1417 | 1418 | [[package]] 1419 | name = "windows_x86_64_gnullvm" 1420 | version = "0.48.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1423 | 1424 | [[package]] 1425 | name = "windows_x86_64_msvc" 1426 | version = "0.48.0" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1429 | --------------------------------------------------------------------------------