├── .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) {
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 {
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 {
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 {
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 {
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],
142 | min: usize,
143 | max: usize,
144 | bucket_size: usize,
145 | buckets: &[Vec],
146 | weights: &[Vec],
147 | gamma: usize,
148 | ) -> Vec {
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(
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>) {
200 | // create a seeded prng
201 | let mut seed = ::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 |
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 |
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 |
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
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 {
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(¶ms, &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(¶ms, &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,
38 | pub b: Vec,
39 | }
40 |
41 | pub struct PvwPublicKey {
42 | a: Array2,
43 | b: Array2,
44 | seed: ::Seed,
45 | par: PvwParameters,
46 | }
47 |
48 | impl PvwPublicKey {
49 | pub fn encrypt(&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,
103 | pub par: PvwParameters,
104 | }
105 |
106 | impl PvwSecretKey {
107 | pub fn random(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(&self, rng: &mut R) -> PvwPublicKey {
123 | let q = Modulus::new(self.par.q);
124 |
125 | let mut seed = ::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::()) + *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 {
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::()));
182 | (d >= self.par.q / 2) as u64
183 | })
184 | .collect()
185 | }
186 |
187 | pub fn decrypt_shifted(&self, ct: PvwCiphertext) -> Vec {
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::())),
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(¶ms, &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(¶ms, &mut rng);
273 | let pk = sk.public_key(&mut rng);
274 |
275 | let sk1 = PvwSecretKey::random(¶ms, &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(¶ms, &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, ¶ms);
307 | let ct_back = PvwCiphertext::try_from_with_parameters(&proto, ¶ms);
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 {
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 {
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> {
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 = 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 {
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 {
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) -> 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 {
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 {
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(
13 | evaluator: &Evaluator,
14 | bfv_sk: &SecretKey,
15 | pvw_sk: &PvwSecretKey,
16 | rng: &mut R,
17 | ) -> Vec {
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(
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(
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, Vec) {
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 {
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],
131 | weights: &[Vec],
132 | k: usize,
133 | gamma: usize,
134 | set_size: usize,
135 | ) -> Vec> {
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> {
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 {
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>, 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>,
184 | mut rhs: Vec>,
185 | k: usize,
186 | modq: u64,
187 | ) -> Vec> {
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::() % 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> {
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, Array2) {
78 | debug_assert!(sec_len <= 512);
79 |
80 | let shape = s.c_ref()[0].coefficients().shape();
81 | let mut d_u128 = ndarray::Array2::::zeros((shape[0], shape[1]));
82 | let mut d1_u128 = ndarray::Array2::::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 {
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, b: &Array2) {
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, Array2) {
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],
203 | rtg: &GaloisKey,
204 | sk: &SecretKey,
205 | ) -> Vec {
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, 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,
50 | constants_outer_offset: usize,
51 | level: usize,
52 | ) -> Ciphertext {
53 | let mut res0 = Array2::::zeros((poly_ctx.moduli_count(), poly_ctx.degree()));
54 | let mut res1 = Array2::::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,
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::::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::::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(¶ms, &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, Vec, Vec) {
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, Array2) {
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::::zeros((coeff_shape[0], coeff_shape[1]));
66 | let mut res1 = Array2::::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, b: &Array2) {
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 {
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],
181 | start: usize,
182 | end: usize,
183 | level: usize,
184 | buckets: &[Vec],
185 | weights: &[Vec],
186 | sk: &SecretKey,
187 | ) -> ((Array2, Array2), (Array2, Array2)) {
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],
254 | level: usize,
255 | buckets: &[Vec],
256 | weights: &[Vec],
257 | sk: &SecretKey,
258 | ) -> ((Array2, Array2), (Array2, Array2)) {
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],
318 | buckets: &[Vec],
319 | weights: &[Vec],
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(¶ms, &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(¶ms, &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,
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,
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,
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(¶ms, precompute_level);
385 |
386 | let sk = SecretKey::random_with_params(¶ms, &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(¶ms, &mut rng);
41 | let ek = EvaluationKey::new(¶ms, &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(¶ms, &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(¶ms, &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(¶ms, level);
154 |
155 | let sk = SecretKey::random_with_params(¶ms, &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(¶ms, &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(¶ms, &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(¶ms, &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(¶ms, &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,
422 | precomp_sub_from_one: &[u64],
423 | ek: &EvaluationKey,
424 | sk: &SecretKey,
425 | #[cfg(feature = "precomp_pvw")] precomputed_pvw_sk_cts: &[Vec],
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],
509 | weights: &[Vec],
510 | payloads: &[Vec],
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],
565 | weights: &[Vec],
566 | ) -> Vec> {
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::().map_or(0, |o| o));
609 |
610 | if threads.is_some() {
611 | let threads = threads
612 | .unwrap()
613 | .parse::()
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 |
--------------------------------------------------------------------------------