From 48407ccefc8323ec52a0a5f2a16c793780d5263d Mon Sep 17 00:00:00 2001 From: Rasoul Akhavan Mahdavi Date: Mon, 1 Dec 2025 00:41:31 -0500 Subject: [PATCH] Examples, benchmarks, and minor changes for consistency, in BDD API --- poulpy-schemes/Cargo.toml | 8 + poulpy-schemes/README.md | 2 +- poulpy-schemes/benches/bdd_arithmetic.rs | 454 ++++++++++++++++++ poulpy-schemes/benches/bdd_prepare.rs | 238 +++++++++ .../benches/circuit_bootstrapping.rs | 13 +- poulpy-schemes/examples/bdd_arithmetic.rs | 332 +++++++++++++ .../examples/circuit_bootstrapping.rs | 18 +- poulpy-schemes/examples/max_array.rs | 259 ++++++++++ .../bin_fhe/bdd_arithmetic/bdd_2w_to_1w.rs | 20 + .../bin_fhe/bdd_arithmetic/blind_selection.rs | 4 +- .../bdd_arithmetic/ciphertexts/fhe_uint.rs | 12 +- .../src/bin_fhe/bdd_arithmetic/key.rs | 18 +- .../tests/test_suite/glwe_blind_selection.rs | 4 +- .../bdd_arithmetic/tests/test_suite/mod.rs | 12 +- .../src/bin_fhe/circuit_bootstrapping/key.rs | 12 +- .../tests/circuit_bootstrapping.rs | 12 +- 16 files changed, 1364 insertions(+), 54 deletions(-) create mode 100644 poulpy-schemes/benches/bdd_arithmetic.rs create mode 100644 poulpy-schemes/benches/bdd_prepare.rs create mode 100644 poulpy-schemes/examples/bdd_arithmetic.rs create mode 100644 poulpy-schemes/examples/max_array.rs diff --git a/poulpy-schemes/Cargo.toml b/poulpy-schemes/Cargo.toml index 34d15aa..4464955 100644 --- a/poulpy-schemes/Cargo.toml +++ b/poulpy-schemes/Cargo.toml @@ -25,4 +25,12 @@ paste = "1.0.15" [[bench]] name = "circuit_bootstrapping" +harness = false + +[[bench]] +name = "bdd_prepare" +harness = false + +[[bench]] +name = "bdd_arithmetic" harness = false \ No newline at end of file diff --git a/poulpy-schemes/README.md b/poulpy-schemes/README.md index 8701a6d..ece3106 100644 --- a/poulpy-schemes/README.md +++ b/poulpy-schemes/README.md @@ -9,7 +9,7 @@ See [./examples/circuit_bootstrapping.rs](./examples/circuit_bootstrapping.rs) ## Available Schemes - **BIN FHE**: - - **bdd_arithmetic**: high level API for u32 arithmetic (u8 to u256 planned) using binary decision circuits. Also provides API for blind retrieval, blind rotation (using encpypted integers) and blind selection. + - **bdd_arithmetic**: high level API for u32 arithmetic (u8 to u256 planned) using binary decision circuits. Also provides API for blind retrieval, blind rotation (using encrypted integers) and blind selection. - **blind_rotation**: API for blind rotation (LWE(m) -> GLWE(X^m)) - **circuit_bootstrapping**: API for circuit bootstrapping (LWE(m) -> GGSW(m) or GGSW(X^m)). - **CKKS**: planned \ No newline at end of file diff --git a/poulpy-schemes/benches/bdd_arithmetic.rs b/poulpy-schemes/benches/bdd_arithmetic.rs new file mode 100644 index 0000000..ed48d13 --- /dev/null +++ b/poulpy-schemes/benches/bdd_arithmetic.rs @@ -0,0 +1,454 @@ +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use poulpy_core::{ + GGSWNoise, GLWEDecrypt, GLWEEncryptSk, GLWEExternalProduct, LWEEncryptSk, ScratchTakeCore, + layouts::{ + Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GGSWPreparedFactory, GLWEAutomorphismKeyLayout, + GLWELayout, GLWESecret, GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout, + LWESecret, Rank, TorusPrecision, + }, +}; + +#[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] +pub use poulpy_cpu_avx::FFT64Avx as BackendImpl; + +#[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] +pub use poulpy_cpu_ref::FFT64Ref as BackendImpl; + +use poulpy_hal::{ + api::{ModuleN, ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxRotateInplace}, + layouts::{Backend, Module, Scratch, ScratchOwned}, + source::Source, +}; +use poulpy_schemes::bin_fhe::{ + bdd_arithmetic::{ + Add, And, BDDKey, BDDKeyEncryptSk, BDDKeyLayout, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint, + FheUintPrepare, FheUintPrepared, Or, Sll, Slt, Sltu, Sra, Srl, Sub, Xor, + }, + blind_rotation::{ + BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory, BlindRotationKeyInfos, BlindRotationKeyLayout, CGGI, + }, + circuit_bootstrapping::{ + CircuitBootstrappingKey, CircuitBootstrappingKeyEncryptSk, CircuitBootstrappingKeyLayout, + CircuitBootstrappingKeyPrepared, CircuitBootstrappingKeyPreparedFactory, CirtuitBootstrappingExecute, + }, +}; + +// Common setup data structure +struct BenchmarkSetup { + module: Module, + scratch: ScratchOwned, + a_enc_prepared: FheUintPrepared, u32, BE>, + b_enc_prepared: FheUintPrepared, u32, BE>, + bdd_key_prepared: BDDKeyPrepared, BRA, BE>, + glwe_layout: GLWELayout, +} + +struct Params { + name: String, + block_size: usize, + glwe_layout: GLWELayout, + ggsw_layout: GGSWLayout, + bdd_layout: BDDKeyLayout, +} + +fn setup_benchmark(params: &Params) -> BenchmarkSetup +where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + CircuitBootstrappingKeyEncryptSk + + CircuitBootstrappingKeyPreparedFactory + + CirtuitBootstrappingExecute + + GGSWPreparedFactory + + GGSWNoise + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W, + BlindRotationKey, BRA>: BlindRotationKeyFactory, + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, +{ + // Scratch space (16MB) + let mut scratch: ScratchOwned = ScratchOwned::alloc(1 << 24); + + let n_glwe: poulpy_core::layouts::Degree = params.bdd_layout.cbt_layout.brk_layout.n_glwe(); + let n_lwe: poulpy_core::layouts::Degree = params.bdd_layout.cbt_layout.brk_layout.n_lwe(); + let rank: poulpy_core::layouts::Rank = params.bdd_layout.cbt_layout.brk_layout.rank; + + let module: Module = Module::::new(n_glwe.as_u32() as u64); + + let mut source_xs: Source = Source::new([1u8; 32]); + let mut source_xa: Source = Source::new([1u8; 32]); + let mut source_xe: Source = Source::new([1u8; 32]); + + let mut sk_lwe: LWESecret> = LWESecret::alloc(n_lwe); + sk_lwe.fill_binary_block(params.block_size, &mut source_xs); + + let mut sk_glwe: GLWESecret> = GLWESecret::alloc(n_glwe, rank); + sk_glwe.fill_ternary_prob(0.5, &mut source_xs); + + // Circuit bootstrapping evaluation key + let mut cbt_key: CircuitBootstrappingKey, BRA> = + CircuitBootstrappingKey::alloc_from_infos(¶ms.bdd_layout.cbt_layout); + cbt_key.encrypt_sk( + &module, + &sk_lwe, + &sk_glwe, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let mut cbt_key_prepared: CircuitBootstrappingKeyPrepared, BRA, BE> = + CircuitBootstrappingKeyPrepared::alloc_from_infos(&module, ¶ms.bdd_layout.cbt_layout); + cbt_key_prepared.prepare(&module, &cbt_key, scratch.borrow()); + + let mut sk_glwe_prepared = GLWESecretPrepared::alloc_from_infos(&module, ¶ms.glwe_layout); + sk_glwe_prepared.prepare(&module, &sk_glwe); + + let mut bdd_key: BDDKey, BRA> = BDDKey::alloc_from_infos(¶ms.bdd_layout); + bdd_key.encrypt_sk( + &module, + &sk_lwe, + &sk_glwe, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let input_a = 255_u32; + let input_b = 30_u32; + + let mut a_enc: FheUint, u32> = FheUint::alloc_from_infos(¶ms.glwe_layout); + a_enc.encrypt_sk( + &module, + input_a, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let mut b_enc: FheUint, u32> = FheUint::alloc_from_infos(¶ms.glwe_layout); + b_enc.encrypt_sk( + &module, + input_b, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + //////// Homomorphic computation starts here //////// + + // Preparing the BDD Key + // The BDD key must be prepared once before any operation is performed + let mut bdd_key_prepared: BDDKeyPrepared, BRA, BE> = BDDKeyPrepared::alloc_from_infos(&module, ¶ms.bdd_layout); + bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow()); + + // Input Preparation + // Before each operation, the inputs to that operation must be prepared + // Preparation extracts each bit of the integer into a seperate GLWE ciphertext and bootstraps it into a GGSW ciphertext + let mut a_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, ¶ms.ggsw_layout); + a_enc_prepared.prepare(&module, &a_enc, &bdd_key_prepared, scratch.borrow()); + + let mut b_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, ¶ms.ggsw_layout); + b_enc_prepared.prepare(&module, &b_enc, &bdd_key_prepared, scratch.borrow()); + + BenchmarkSetup { + module, + scratch, + a_enc_prepared, + b_enc_prepared, + bdd_key_prepared, + glwe_layout: params.glwe_layout.clone(), + } +} + +fn create_runner(setup: BenchmarkSetup, operation: F) -> impl FnMut() +where + Module: ExecuteBDDCircuit2WTo1W, + Scratch: ScratchTakeCore, + ScratchOwned: ScratchOwnedBorrow, + F: Fn( + &mut FheUint, u32>, + &Module, + &FheUintPrepared, u32, BE>, + &FheUintPrepared, u32, BE>, + &BDDKeyPrepared, BRA, BE>, + &mut Scratch, + ), +{ + let BenchmarkSetup { + module, + mut scratch, + a_enc_prepared, + b_enc_prepared, + bdd_key_prepared, + glwe_layout, + } = setup; + + let mut c_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + + move || { + operation( + &mut c_enc, + &module, + &a_enc_prepared, + &b_enc_prepared, + &bdd_key_prepared, + scratch.borrow(), + ); + black_box(()); + } +} + +fn bench_operation( + group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>, + params: &Params, + operation_name: &str, + operation: F, +) where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + CircuitBootstrappingKeyEncryptSk + + CircuitBootstrappingKeyPreparedFactory + + CirtuitBootstrappingExecute + + GGSWPreparedFactory + + GGSWNoise + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W, + BlindRotationKey, BRA>: BlindRotationKeyFactory, + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, + F: Fn( + &mut FheUint, u32>, + &Module, + &FheUintPrepared, u32, BE>, + &FheUintPrepared, u32, BE>, + &BDDKeyPrepared, BRA, BE>, + &mut Scratch, + ) + 'static, +{ + let setup = setup_benchmark::(params); + let mut runner = create_runner(setup, operation); + let id = BenchmarkId::from_parameter(format!("{}_{}", params.name, operation_name)); + group.bench_with_input(id, &(), |b, _| b.iter(&mut runner)); +} + +pub fn benc_bdd_arithmetic(c: &mut Criterion, label: &str) +where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + CircuitBootstrappingKeyEncryptSk + + CircuitBootstrappingKeyPreparedFactory + + CirtuitBootstrappingExecute + + GGSWPreparedFactory + + GGSWNoise + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W, + BlindRotationKey, BRA>: BlindRotationKeyFactory, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, +{ + let group_name: String = format!("bdd_arithmetic::{label}"); + + let mut group = c.benchmark_group(group_name); + + const N_GLWE: u32 = 1024; + const N_LWE: u32 = 679; + const BINARY_BLOCK_SIZE: u32 = 7; + const BASE2K: u32 = 15; + const RANK: u32 = 2; + + let params: Params = Params { + name: String::from(format!("n_glwe={N_GLWE}")), + block_size: BINARY_BLOCK_SIZE as usize, + glwe_layout: GLWELayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(2 * BASE2K), + rank: Rank(RANK), + }, + ggsw_layout: GGSWLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(3 * BASE2K), + rank: Rank(RANK), + dnum: Dnum(3), + dsize: Dsize(1), + }, + bdd_layout: BDDKeyLayout { + cbt_layout: CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { + n_glwe: Degree(N_GLWE), + n_lwe: Degree(N_LWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + rank: Rank(RANK), + }, + atk_layout: GLWEAutomorphismKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + tsk_layout: GGLWEToGGSWKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + }, + ks_glwe_layout: Some(GLWESwitchingKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank_in: Rank(RANK), + rank_out: Rank(1), + }), + ks_lwe_layout: GLWEToLWEKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + rank_in: Rank(1), + dnum: Dnum(4), + }, + }, + }; + + // Benchmark each operation + bench_operation::( + &mut group, + ¶ms, + "add", + |c_enc, module, a, b, key, scratch| { + c_enc.add(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "sub", + |c_enc, module, a, b, key, scratch| { + c_enc.sub(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "sll", + |c_enc, module, a, b, key, scratch| { + c_enc.sll(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "sra", + |c_enc, module, a, b, key, scratch| { + c_enc.sra(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "srl", + |c_enc, module, a, b, key, scratch| { + c_enc.srl(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "slt", + |c_enc, module, a, b, key, scratch| { + c_enc.slt(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "sltu", + |c_enc, module, a, b, key, scratch| { + c_enc.sltu(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "or", + |c_enc, module, a, b, key, scratch| { + c_enc.or(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "and", + |c_enc, module, a, b, key, scratch| { + c_enc.and(module, a, b, key, scratch); + }, + ); + + bench_operation::( + &mut group, + ¶ms, + "xor", + |c_enc, module, a, b, key, scratch| { + c_enc.xor(module, a, b, key, scratch); + }, + ); + + group.finish(); +} + +fn bench_bdd_arithmetic_fft64(c: &mut Criterion) { + #[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] + let label = "fft64_avx"; + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + let label = "fft64_ref"; + benc_bdd_arithmetic::(c, label); +} + +criterion_group!(benches, bench_bdd_arithmetic_fft64); +criterion_main!(benches); diff --git a/poulpy-schemes/benches/bdd_prepare.rs b/poulpy-schemes/benches/bdd_prepare.rs new file mode 100644 index 0000000..f1a3edf --- /dev/null +++ b/poulpy-schemes/benches/bdd_prepare.rs @@ -0,0 +1,238 @@ +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use poulpy_core::{ + GGSWNoise, GLWEDecrypt, GLWEEncryptSk, GLWEExternalProduct, LWEEncryptSk, ScratchTakeCore, + layouts::{ + Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GGSWPreparedFactory, GLWEAutomorphismKeyLayout, + GLWELayout, GLWESecret, GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout, + LWESecret, Rank, TorusPrecision, + }, +}; + +#[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] +pub use poulpy_cpu_avx::FFT64Avx as BackendImpl; + +#[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] +pub use poulpy_cpu_ref::FFT64Ref as BackendImpl; + +use poulpy_hal::{ + api::{ModuleN, ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxRotateInplace}, + layouts::{Backend, Module, Scratch, ScratchOwned}, + source::Source, +}; +use poulpy_schemes::bin_fhe::{ + bdd_arithmetic::{ + BDDKey, BDDKeyEncryptSk, BDDKeyLayout, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint, + FheUintPrepare, FheUintPrepared, + }, + blind_rotation::{ + BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory, BlindRotationKeyInfos, BlindRotationKeyLayout, CGGI, + }, + circuit_bootstrapping::{ + CircuitBootstrappingKeyEncryptSk, CircuitBootstrappingKeyLayout, CircuitBootstrappingKeyPreparedFactory, + CirtuitBootstrappingExecute, + }, +}; + +pub fn benc_bdd_prepare(c: &mut Criterion, label: &str) +where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + CircuitBootstrappingKeyEncryptSk + + CircuitBootstrappingKeyPreparedFactory + + CirtuitBootstrappingExecute + + GGSWPreparedFactory + + GGSWNoise + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W, + BlindRotationKey, BRA>: BlindRotationKeyFactory, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, +{ + let group_name: String = format!("bdd_arithmetic::{label}"); + + let mut group = c.benchmark_group(group_name); + + struct Params { + name: String, + block_size: usize, + glwe_layout: GLWELayout, + ggsw_layout: GGSWLayout, + bdd_layout: BDDKeyLayout, + } + + fn runner(params: &Params) -> impl FnMut() + where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + CircuitBootstrappingKeyEncryptSk + + CircuitBootstrappingKeyPreparedFactory + + CirtuitBootstrappingExecute + + GGSWPreparedFactory + + GGSWNoise + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W, + BlindRotationKey, BRA>: BlindRotationKeyFactory, /* TODO find a way to remove this bound or move it to CBT KEY */ + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, + { + // Scratch space (4MB) + let mut scratch: ScratchOwned = ScratchOwned::alloc(1 << 25); + + let n_glwe: poulpy_core::layouts::Degree = params.bdd_layout.cbt_layout.brk_layout.n_glwe(); + let n_lwe: poulpy_core::layouts::Degree = params.bdd_layout.cbt_layout.brk_layout.n_lwe(); + let rank: poulpy_core::layouts::Rank = params.bdd_layout.cbt_layout.brk_layout.rank; + + let module: Module = Module::::new(n_glwe.as_u32() as u64); + + let mut source_xs: Source = Source::new([1u8; 32]); + let mut source_xa: Source = Source::new([1u8; 32]); + let mut source_xe: Source = Source::new([1u8; 32]); + + let mut sk_lwe: LWESecret> = LWESecret::alloc(n_lwe); + sk_lwe.fill_binary_block(params.block_size, &mut source_xs); + + let mut sk_glwe: GLWESecret> = GLWESecret::alloc(n_glwe, rank); + sk_glwe.fill_ternary_prob(0.5, &mut source_xs); + + let mut sk_glwe_prepared = GLWESecretPrepared::alloc_from_infos(&module, ¶ms.glwe_layout); + sk_glwe_prepared.prepare(&module, &sk_glwe); + + let mut bdd_key: BDDKey, BRA> = BDDKey::alloc_from_infos(¶ms.bdd_layout); + bdd_key.encrypt_sk( + &module, + &sk_lwe, + &sk_glwe, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let input_a = 255_u32; + + let mut a_enc: FheUint, u32> = FheUint::alloc_from_infos(¶ms.glwe_layout); + a_enc.encrypt_sk( + &module, + input_a, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let mut bdd_key_prepared: BDDKeyPrepared, BRA, BE> = + BDDKeyPrepared::alloc_from_infos(&module, ¶ms.bdd_layout); + bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow()); + + let mut a_enc_prepared: FheUintPrepared, u32, BE> = + FheUintPrepared::alloc_from_infos(&module, ¶ms.ggsw_layout); + + move || { + a_enc_prepared.prepare(&module, &a_enc, &bdd_key_prepared, scratch.borrow()); + black_box(()); + } + } + + const N_GLWE: u32 = 1024; + const N_LWE: u32 = 679; + const BINARY_BLOCK_SIZE: u32 = 7; + const BASE2K: u32 = 15; + const RANK: u32 = 2; + + let params: Params = Params { + name: String::from(format!("n_glwe={N_GLWE}")), + block_size: BINARY_BLOCK_SIZE as usize, + glwe_layout: GLWELayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(2 * BASE2K), + rank: Rank(RANK), + }, + ggsw_layout: GGSWLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(3 * BASE2K), + rank: Rank(RANK), + dnum: Dnum(3), + dsize: Dsize(1), + }, + bdd_layout: BDDKeyLayout { + cbt_layout: CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { + n_glwe: Degree(N_GLWE), + n_lwe: Degree(N_LWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + rank: Rank(RANK), + }, + atk_layout: GLWEAutomorphismKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + tsk_layout: GGLWEToGGSWKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + }, + ks_glwe_layout: Some(GLWESwitchingKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank_in: Rank(RANK), + rank_out: Rank(1), + }), + ks_lwe_layout: GLWEToLWEKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + rank_in: Rank(1), + dnum: Dnum(4), + }, + }, + }; + + let id: BenchmarkId = BenchmarkId::from_parameter(params.name.clone()); + let mut runner = runner::(¶ms); + group.bench_with_input(id, &(), |b, _| b.iter(&mut runner)); + + group.finish(); +} + +fn bench_bdd_prepare_fft64(c: &mut Criterion) { + #[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] + let label = "fft64_avx"; + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + let label = "fft64_ref"; + benc_bdd_prepare::(c, label); +} + +criterion_group!(benches, bench_bdd_prepare_fft64); +criterion_main!(benches); diff --git a/poulpy-schemes/benches/circuit_bootstrapping.rs b/poulpy-schemes/benches/circuit_bootstrapping.rs index 79f8336..855fdec 100644 --- a/poulpy-schemes/benches/circuit_bootstrapping.rs +++ b/poulpy-schemes/benches/circuit_bootstrapping.rs @@ -85,9 +85,9 @@ where // Scratch space (4MB) let mut scratch: ScratchOwned = ScratchOwned::alloc(1 << 22); - let n_glwe: poulpy_core::layouts::Degree = params.cbt_infos.layout_brk.n_glwe(); - let n_lwe: poulpy_core::layouts::Degree = params.cbt_infos.layout_brk.n_lwe(); - let rank: poulpy_core::layouts::Rank = params.cbt_infos.layout_brk.rank; + let n_glwe: poulpy_core::layouts::Degree = params.cbt_infos.brk_layout.n_glwe(); + let n_lwe: poulpy_core::layouts::Degree = params.cbt_infos.brk_layout.n_lwe(); + let rank: poulpy_core::layouts::Rank = params.cbt_infos.brk_layout.rank; let module: Module = Module::::new(n_glwe.as_u32() as u64); @@ -97,7 +97,6 @@ where let mut sk_lwe: LWESecret> = LWESecret::alloc(n_lwe); sk_lwe.fill_binary_block(params.block_size, &mut source_xs); - sk_lwe.fill_zero(); let mut sk_glwe: GLWESecret> = GLWESecret::alloc(n_glwe, rank); sk_glwe.fill_ternary_prob(0.5, &mut source_xs); @@ -151,7 +150,7 @@ where rank: 2_u32.into(), }, cbt_infos: CircuitBootstrappingKeyLayout { - layout_brk: BlindRotationKeyLayout { + brk_layout: BlindRotationKeyLayout { n_glwe: 1024_u32.into(), n_lwe: 574_u32.into(), base2k: 13_u32.into(), @@ -159,7 +158,7 @@ where dnum: 3_u32.into(), rank: 2_u32.into(), }, - layout_atk: GLWEAutomorphismKeyLayout { + atk_layout: GLWEAutomorphismKeyLayout { n: 1024_u32.into(), base2k: 13_u32.into(), k: 52_u32.into(), @@ -167,7 +166,7 @@ where dsize: Dsize(1), rank: 2_u32.into(), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: 1024_u32.into(), base2k: 13_u32.into(), k: 52_u32.into(), diff --git a/poulpy-schemes/examples/bdd_arithmetic.rs b/poulpy-schemes/examples/bdd_arithmetic.rs new file mode 100644 index 0000000..a39103b --- /dev/null +++ b/poulpy-schemes/examples/bdd_arithmetic.rs @@ -0,0 +1,332 @@ +use std::collections::HashMap; + +use poulpy_core::{ + GLWEDecrypt, GLWEEncryptSk, GLWEExternalProduct, LWEEncryptSk, ScratchTakeCore, + layouts::{ + Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GGSWPreparedFactory, GLWEAutomorphismKeyLayout, + GLWELayout, GLWESecret, GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout, + LWESecret, Rank, TorusPrecision, + }, +}; +use poulpy_hal::{ + api::{ModuleN, ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxRotateInplace}, + layouts::{Backend, Module, Scratch, ScratchOwned}, + source::Source, +}; +use poulpy_schemes::bin_fhe::{ + bdd_arithmetic::{ + Add, BDDKey, BDDKeyEncryptSk, BDDKeyLayout, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint, + FheUintPrepare, FheUintPrepared, GLWEBlindSelection, Xor, + }, + blind_rotation::{BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory, BlindRotationKeyLayout, CGGI}, + circuit_bootstrapping::CircuitBootstrappingKeyLayout, +}; +use rand::Rng; + +#[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] +use poulpy_cpu_avx::FFT64Avx; +#[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] +use poulpy_cpu_ref::FFT64Ref; + +// This example demonstrates and end-to-end example usage of the BDD arithmetic API +// It includes all steps including: +// +// - Parameter Selection +// - Key Generation +// - Input Encryption +// +// - Key preparation +// - Input Preparation +// - Operation Execution +// +// - Result Decryption +// + +fn example_bdd_arithmetic() +where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + GGSWPreparedFactory + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W + + GLWEBlindSelection, + BlindRotationKey, BRA>: BlindRotationKeyFactory, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, +{ + ////////// Parameter Selection + const N_GLWE: u32 = 1024; + const N_LWE: u32 = 567; + const BINARY_BLOCK_SIZE: u32 = 7; + const BASE2K: u32 = 17; + const RANK: u32 = 1; + + // GLWE layout, used to generate GLWE Ciphertexts, keys, switching keys, etc + let glwe_layout = GLWELayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(2 * BASE2K), + rank: Rank(RANK), + }; + + // Used to generate GGSW Ciphertexts + let ggsw_layout = GGSWLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(3 * BASE2K), + rank: Rank(RANK), + dnum: Dnum(3), + dsize: Dsize(1), + }; + + // Used to generate BDD Keys, for the arithmetic operations + let bdd_layout = BDDKeyLayout { + cbt_layout: CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { + n_glwe: Degree(N_GLWE), + n_lwe: Degree(N_LWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + rank: Rank(RANK), + }, + atk_layout: GLWEAutomorphismKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + tsk_layout: GGLWEToGGSWKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + }, + ks_glwe_layout: Some(GLWESwitchingKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank_in: Rank(RANK), + rank_out: Rank(1), + }), + ks_lwe_layout: GLWEToLWEKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + rank_in: Rank(1), + dnum: Dnum(4), + }, + }; + + let module = Module::::new(N_GLWE as u64); + + // Secret key sampling source + let mut source_xs: Source = Source::new([1u8; 32]); + + // Public randomness sampling source + let mut source_xa: Source = Source::new([1u8; 32]); + + // Noise sampling source + let mut source_xe: Source = Source::new([1u8; 32]); + + // Scratch space (4MB) + let mut scratch: ScratchOwned = ScratchOwned::alloc(1 << 22); + + ////////// Key Generation and Preparation + // Generating the GLWE and LWE key + let mut sk_glwe = GLWESecret::alloc_from_infos(&glwe_layout); + sk_glwe.fill_ternary_prob(0.5, &mut source_xs); + + let mut sk_lwe = LWESecret::alloc(bdd_layout.cbt_layout.brk_layout.n_lwe); + sk_lwe.fill_binary_block(BINARY_BLOCK_SIZE as usize, &mut source_xs); + + // Preparing the private keys + let mut sk_glwe_prepared = GLWESecretPrepared::alloc_from_infos(&module, &glwe_layout); + sk_glwe_prepared.prepare(&module, &sk_glwe); + + // Creating the public BDD Key + // This key is required to prepare all Fhe Integers for operations, + // and for performing the operations themselves + let mut bdd_key: BDDKey, BRA> = BDDKey::alloc_from_infos(&bdd_layout); + bdd_key.encrypt_sk( + &module, + &sk_lwe, + &sk_glwe, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + ////////// Input Encryption + // Encrypting the inputs + let input_a = 255_u32; + let input_b = 30_u32; + + let mut a_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + a_enc.encrypt_sk( + &module, + input_a, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + let mut b_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + b_enc.encrypt_sk( + &module, + input_b, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + //////// Homomorphic computation starts here //////// + + // Preparing the BDD Key + // The BDD key must be prepared once before any operation is performed + let mut bdd_key_prepared: BDDKeyPrepared, BRA, BE> = BDDKeyPrepared::alloc_from_infos(&module, &bdd_layout); + bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow()); + + // Input Preparation + // Before each operation, the inputs to that operation must be prepared + // Preparation extracts each bit of the integer into a seperate GLWE ciphertext and bootstraps it into a GGSW ciphertext + let mut a_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + a_enc_prepared.prepare(&module, &a_enc, &bdd_key_prepared, scratch.borrow()); + + let mut b_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + b_enc_prepared.prepare(&module, &b_enc, &bdd_key_prepared, scratch.borrow()); + + // Allocating the intermediate ciphertext c_enc + let mut c_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + + // Performing the operation + c_enc.add( + &module, + &a_enc_prepared, + &b_enc_prepared, + &bdd_key_prepared, + scratch.borrow(), + ); + + // Preparing the intermediate result ciphertext, c_enc, for the next operation + let mut c_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + c_enc_prepared.prepare(&module, &c_enc, &bdd_key_prepared, scratch.borrow()); + + // Creating the output ciphertext d_enc + let mut selected_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + selected_enc.xor( + &module, + &c_enc_prepared, + &a_enc_prepared, + &bdd_key_prepared, + scratch.borrow(), + ); + + //////// Homomorphic computation ends here //////// + + // Decrypting the result + let d_dec = selected_enc.decrypt(&module, &sk_glwe_prepared, scratch.borrow()); + + // d = (a + b) ^ a + let d_correct = (input_a.wrapping_add(input_b)) ^ input_a; + println!("Result: {} == {}", d_dec, d_correct); + + // List of available operations are: + // - add: addition + // - sub: subtraction + // - sll: left shift logical + // - sra: right shift arithmetic + // - srl: right shift logical + // - slt: less than + // - sltu: less than unsigned + // - and: bitwise and + // - or: bitwise or + // - xor: bitwise xor + + ///////////////////////////// GLWE Blind Selection + // This example demonstrates the use of the GLWE Blind Selection operation + // It can choose between any number of encrypted fheuint inputs + // using an encrypted fheuint selector + + let log_2_number_of_inputs: usize = 5; + let number_of_inputs: usize = 1 << log_2_number_of_inputs; + let inputs_a_vec: Vec = (0..number_of_inputs) + .map(|_| rand::rng().random_range(0..u32::MAX - 1)) + .collect(); + let input_selector: u32 = rand::rng().random_range(0..number_of_inputs as u32); + + let mut inputs_a_enc_vec: Vec, u32>> = Vec::new(); + for input in &inputs_a_vec { + let mut next_input: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + next_input.encrypt_sk( + &module, + *input, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + inputs_a_enc_vec.push(next_input); + } + + let mut inputs_a_enc_vec_map: HashMap, u32>> = HashMap::new(); + for (i, input) in inputs_a_enc_vec.iter_mut().enumerate() { + inputs_a_enc_vec_map.insert(i, input); + } + + let mut input_selector_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + input_selector_enc.encrypt_sk( + &module, + input_selector, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + let mut input_selector_enc_prepared: FheUintPrepared, u32, BE> = + FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + input_selector_enc_prepared.prepare( + &module, + &input_selector_enc, + &bdd_key_prepared, + scratch.borrow(), + ); + + module.glwe_blind_selection( + &mut selected_enc, + inputs_a_enc_vec_map, + &input_selector_enc_prepared, + 0, + log_2_number_of_inputs, + scratch.borrow(), + ); + + let selected_dec = selected_enc.decrypt(&module, &sk_glwe_prepared, scratch.borrow()); + let selected_correct = inputs_a_vec[input_selector as usize]; + println!("Result: {} == {}", selected_dec, selected_correct); +} + +fn main() { + #[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] + example_bdd_arithmetic::(); + + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + example_bdd_arithmetic::(); +} diff --git a/poulpy-schemes/examples/circuit_bootstrapping.rs b/poulpy-schemes/examples/circuit_bootstrapping.rs index 8ab8909..7a0b30a 100644 --- a/poulpy-schemes/examples/circuit_bootstrapping.rs +++ b/poulpy-schemes/examples/circuit_bootstrapping.rs @@ -77,8 +77,8 @@ fn main() { // GGLWE tensor key modulus let k_tsk: usize = (rows_tsk + 1) * base2k; - let cbt_infos: CircuitBootstrappingKeyLayout = CircuitBootstrappingKeyLayout { - layout_brk: BlindRotationKeyLayout { + let cbt_layout: CircuitBootstrappingKeyLayout = CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { n_glwe: n_glwe.into(), n_lwe: n_lwe.into(), base2k: base2k.into(), @@ -86,7 +86,7 @@ fn main() { dnum: rows_brk.into(), rank: rank.into(), }, - layout_atk: GLWEAutomorphismKeyLayout { + atk_layout: GLWEAutomorphismKeyLayout { n: n_glwe.into(), base2k: base2k.into(), k: k_trace.into(), @@ -94,7 +94,7 @@ fn main() { dsize: 1_u32.into(), rank: rank.into(), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: n_glwe.into(), base2k: base2k.into(), k: k_tsk.into(), @@ -134,12 +134,12 @@ fn main() { // LWE secret let mut sk_lwe: LWESecret> = LWESecret::alloc(n_lwe.into()); sk_lwe.fill_binary_block(block_size, &mut source_xs); - sk_lwe.fill_zero(); + // sk_lwe.fill_zero(); // for testing // GLWE secret let mut sk_glwe: GLWESecret> = GLWESecret::alloc(n_glwe.into(), rank.into()); sk_glwe.fill_ternary_prob(0.5, &mut source_xs); - // sk_glwe.fill_zero(); + // sk_glwe.fill_zero(); // for testing // GLWE secret prepared (opaque backend dependant write only struct) let mut sk_glwe_prepared: GLWESecretPrepared, BackendImpl> = GLWESecretPrepared::alloc(&module, rank.into()); @@ -151,7 +151,7 @@ fn main() { // LWE plaintext let mut pt_lwe: LWEPlaintext> = LWEPlaintext::alloc(base2k.into(), k_lwe_pt.into()); - // LWE plaintext(data * 2^{- (k_lwe_pt - 1)}) + // LWE plaintext(data * 2^{- (k_lwe_pt + 1)}) pt_lwe.encode_i64(data, (k_lwe_pt + 1).into()); // +1 for padding bit // Normalize plaintext to nicely print coefficients @@ -174,7 +174,7 @@ fn main() { let now: Instant = Instant::now(); // Circuit bootstrapping evaluation key - let mut cbt_key: CircuitBootstrappingKey, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_infos); + let mut cbt_key: CircuitBootstrappingKey, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_layout); cbt_key.encrypt_sk( &module, @@ -192,7 +192,7 @@ fn main() { // Circuit bootstrapping key prepared (opaque backend dependant write only struct) let mut cbt_prepared: CircuitBootstrappingKeyPrepared, CGGI, BackendImpl> = - CircuitBootstrappingKeyPrepared::alloc_from_infos(&module, &cbt_infos); + CircuitBootstrappingKeyPrepared::alloc_from_infos(&module, &cbt_layout); cbt_prepared.prepare(&module, &cbt_key, scratch.borrow()); // Apply circuit bootstrapping: LWE(data * 2^{- (k_lwe_pt + 2)}) -> GGSW(data) diff --git a/poulpy-schemes/examples/max_array.rs b/poulpy-schemes/examples/max_array.rs new file mode 100644 index 0000000..dc3c8af --- /dev/null +++ b/poulpy-schemes/examples/max_array.rs @@ -0,0 +1,259 @@ +use std::collections::HashMap; + +use poulpy_core::{ + GLWECopy, GLWEDecrypt, GLWEEncryptSk, GLWEExternalProduct, LWEEncryptSk, ScratchTakeCore, + layouts::{ + Base2K, Degree, Dnum, Dsize, GGLWEToGGSWKeyLayout, GGSWLayout, GGSWPreparedFactory, GLWEAutomorphismKeyLayout, + GLWELayout, GLWESecret, GLWESecretPrepared, GLWESecretPreparedFactory, GLWESwitchingKeyLayout, GLWEToLWEKeyLayout, + GLWEToMut, GLWEToRef, LWESecret, Rank, TorusPrecision, + }, +}; +use poulpy_hal::{ + api::{ModuleN, ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxRotateInplace}, + layouts::{Backend, Module, Scratch, ScratchOwned}, + source::Source, +}; +use poulpy_schemes::bin_fhe::{ + bdd_arithmetic::{ + BDDKey, BDDKeyEncryptSk, BDDKeyLayout, BDDKeyPrepared, BDDKeyPreparedFactory, ExecuteBDDCircuit2WTo1W, FheUint, + FheUintPrepare, FheUintPrepared, GLWEBlindSelection, Sltu, + }, + blind_rotation::{BlindRotationAlgo, BlindRotationKey, BlindRotationKeyFactory, BlindRotationKeyLayout, CGGI}, + circuit_bootstrapping::CircuitBootstrappingKeyLayout, +}; +use rand::Rng; + +#[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] +use poulpy_cpu_avx::FFT64Avx; +#[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] +use poulpy_cpu_ref::FFT64Ref; + +// This example demonstrates and end-to-end example usage of the BDD arithmetic API +// to compute the maximum of an array of integers. + +fn example_max_array() +where + Module: ModuleNew + + ModuleN + + GLWESecretPreparedFactory + + GLWEExternalProduct + + GLWEDecrypt + + LWEEncryptSk + + GGSWPreparedFactory + + GLWEEncryptSk + + VecZnxRotateInplace + + BDDKeyEncryptSk + + BDDKeyPreparedFactory + + FheUintPrepare + + ExecuteBDDCircuit2WTo1W + + GLWEBlindSelection, + BlindRotationKey, BRA>: BlindRotationKeyFactory, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, + Scratch: ScratchTakeCore, +{ + ////////// Parameter Selection + const N_GLWE: u32 = 1024; + const N_LWE: u32 = 567; + const BINARY_BLOCK_SIZE: u32 = 7; + const BASE2K: u32 = 17; + const RANK: u32 = 1; + + // GLWE layout, used to generate GLWE Ciphertexts, keys, switching keys, etc + let glwe_layout = GLWELayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(2 * BASE2K), + rank: Rank(RANK), + }; + + // Used to generate GGSW Ciphertexts + let ggsw_layout = GGSWLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(3 * BASE2K), + rank: Rank(RANK), + dnum: Dnum(3), + dsize: Dsize(1), + }; + + // Used to generate CBT Keys + let cbt_layout = CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { + n_glwe: Degree(N_GLWE), + n_lwe: Degree(N_LWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + rank: Rank(RANK), + }, + atk_layout: GLWEAutomorphismKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + tsk_layout: GGLWEToGGSWKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank: Rank(RANK), + }, + }; + + // Used to generate BDD Keys, for the arithmetic operations + let bdd_layout = BDDKeyLayout { + cbt_layout: cbt_layout, + ks_glwe_layout: Some(GLWESwitchingKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + dnum: Dnum(4), + dsize: Dsize(1), + rank_in: Rank(RANK), + rank_out: Rank(1), + }), + ks_lwe_layout: GLWEToLWEKeyLayout { + n: Degree(N_GLWE), + base2k: Base2K(BASE2K), + k: TorusPrecision(4 * BASE2K), + rank_in: Rank(RANK), + dnum: Dnum(4), + }, + }; + + let module = Module::::new(N_GLWE as u64); + + // Secret key sampling source + let mut source_xs: Source = Source::new([1u8; 32]); + + // Public randomness sampling source + let mut source_xa: Source = Source::new([1u8; 32]); + + // Noise sampling source + let mut source_xe: Source = Source::new([1u8; 32]); + + // Scratch space (4MB) + let mut scratch: ScratchOwned = ScratchOwned::alloc(1 << 22); + + ////////// Key Generation and Preparation + // Generating the GLWE and LWE key + let mut sk_glwe = GLWESecret::alloc_from_infos(&glwe_layout); + sk_glwe.fill_ternary_prob(0.5, &mut source_xs); + + let mut sk_lwe = LWESecret::alloc(Degree(N_LWE)); + sk_lwe.fill_binary_block(BINARY_BLOCK_SIZE as usize, &mut source_xs); + + // Preparing the private keys + let mut sk_glwe_prepared = GLWESecretPrepared::alloc_from_infos(&module, &glwe_layout); + sk_glwe_prepared.prepare(&module, &sk_glwe); + + // Creating the public BDD Key + // This key is required to prepare all Fhe Integers for operations, + // and for performing the operations themselves + let mut bdd_key: BDDKey, BRA> = BDDKey::alloc_from_infos(&bdd_layout); + bdd_key.encrypt_sk( + &module, + &sk_lwe, + &sk_glwe, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + + ////////// Input Encryption + // Encrypting the inputs + let mut rng = rand::rng(); + let inputs: Vec = (0..3).map(|_| rng.random_range(0..u32::MAX - 1)).collect(); + + let mut inputs_enc: Vec, u32>> = Vec::new(); + for input in &inputs { + let mut next_input = FheUint::alloc_from_infos(&glwe_layout); + next_input.encrypt_sk( + &module, + *input, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + inputs_enc.push(next_input); + } + + //////// Homomorphic computation starts here //////// + + // Preparing the BDD Key + // The BDD key must be prepared once before any operation is performed + let mut bdd_key_prepared: BDDKeyPrepared, BRA, BE> = BDDKeyPrepared::alloc_from_infos(&module, &bdd_layout); + bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow()); + + let mut max_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + max_enc.encrypt_sk( + &module, + 0, + &sk_glwe_prepared, + &mut source_xa, + &mut source_xe, + scratch.borrow(), + ); + // Copy of max_enc for the HashMap + let mut max_enc_copy: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + + // Allocating the intermediate ciphertext c_enc + let mut compare_enc: FheUint, u32> = FheUint::alloc_from_infos(&glwe_layout); + let mut compare_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + + for input_i in inputs_enc.iter_mut() { + let mut max_enc_prepared: FheUintPrepared, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + max_enc_prepared.prepare(&module, &max_enc, &bdd_key_prepared, scratch.borrow()); + + let mut input_i_enc_prepared: FheUintPrepared, u32, BE> = + FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + input_i_enc_prepared.prepare(&module, &input_i, &bdd_key_prepared, scratch.borrow()); + + // b = (input_i < max) + compare_enc.sltu( + &module, + &input_i_enc_prepared, + &max_enc_prepared, + &bdd_key_prepared, + scratch.borrow(), + ); + + compare_enc_prepared.prepare(&module, &compare_enc, &bdd_key_prepared, scratch.borrow()); + + module.glwe_copy(&mut max_enc_copy.to_mut(), &max_enc.to_ref()); + + let cts = HashMap::from([(0, input_i), (1, &mut max_enc_copy)]); + + as GLWEBlindSelection>::glwe_blind_selection( + &module, + &mut max_enc, + cts, + &compare_enc_prepared, + 0, + 1, + scratch.borrow(), + ); + } + + //////// Homomorphic computation ends here //////// + + // Decrypting the result + let result_dec = max_enc.decrypt(&module, &sk_glwe_prepared, scratch.borrow()); + + // result = max of inputs + let result_correct = inputs.iter().max().unwrap(); + println!("Result: {} == {}", result_dec, result_correct); +} + +fn main() { + #[cfg(all(feature = "enable-avx", target_arch = "x86_64"))] + example_max_array::(); + + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + example_max_array::(); +} diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/bdd_2w_to_1w.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/bdd_2w_to_1w.rs index b3c34a6..adf53b0 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/bdd_2w_to_1w.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/bdd_2w_to_1w.rs @@ -188,15 +188,35 @@ macro_rules! impl_bdd_2w_to_1w_trait { } }; } + +// a + b define_bdd_2w_to_1w_trait!(pub Add, add); + +// a - b define_bdd_2w_to_1w_trait!(pub Sub, sub); + +// a << b define_bdd_2w_to_1w_trait!(pub Sll, sll); + +// a >> b arithmetic define_bdd_2w_to_1w_trait!(pub Sra, sra); + +// a >> b logical define_bdd_2w_to_1w_trait!(pub Srl, srl); + +// signed a < signed b define_bdd_2w_to_1w_trait!(pub Slt, slt); + +// unsigned a < unsigned b define_bdd_2w_to_1w_trait!(pub Sltu, sltu); + +// a or b define_bdd_2w_to_1w_trait!(pub Or, or); + +// a and b define_bdd_2w_to_1w_trait!(pub And, and); + +// a xor b define_bdd_2w_to_1w_trait!(pub Xor, xor); impl_bdd_2w_to_1w_trait!( diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/blind_selection.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/blind_selection.rs index 2351446..6a564bf 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/blind_selection.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/blind_selection.rs @@ -8,9 +8,9 @@ use poulpy_hal::layouts::{Backend, Module, Scratch, ZnxZero}; use crate::bin_fhe::bdd_arithmetic::{Cmux, GetGGSWBit, UnsignedInteger}; -impl GLWEBlinSelection for Module where Self: GLWECopy + Cmux + GLWEDecrypt {} +impl GLWEBlindSelection for Module where Self: GLWECopy + Cmux + GLWEDecrypt {} -pub trait GLWEBlinSelection +pub trait GLWEBlindSelection where Self: GLWECopy + Cmux + GLWEDecrypt, { diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/ciphertexts/fhe_uint.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/ciphertexts/fhe_uint.rs index 9f8aed8..5f1fc09 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/ciphertexts/fhe_uint.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/ciphertexts/fhe_uint.rs @@ -87,7 +87,7 @@ impl FheUint { &mut self, module: &M, data: T, - sk: &S, + sk_glwe: &S, source_xa: &mut Source, source_xe: &mut Source, scratch: &mut Scratch, @@ -100,7 +100,7 @@ impl FheUint { { assert!(module.n().is_multiple_of(T::BITS as usize)); assert_eq!(self.n(), module.n() as u32); - assert_eq!(sk.n(), module.n() as u32); + assert_eq!(sk_glwe.n(), module.n() as u32); } let mut data_bits: Vec = vec![0i64; module.n()]; @@ -122,7 +122,7 @@ impl FheUint { pt.encode_vec_i64(&data_bits, TorusPrecision(2)); self.bits - .encrypt_sk(module, &pt, sk, source_xa, source_xe, scratch_1); + .encrypt_sk(module, &pt, sk_glwe, source_xa, source_xe, scratch_1); } } @@ -150,7 +150,7 @@ impl FheUint { self.bits.noise(module, &pt, sk, scratch_1) } - pub fn decrypt(&self, module: &M, sk: &S, scratch: &mut Scratch) -> T + pub fn decrypt(&self, module: &M, sk_glwe: &S, scratch: &mut Scratch) -> T where S: GLWESecretPreparedToRef + GLWEInfos, M: ModuleLogN + GLWEDecrypt, @@ -160,7 +160,7 @@ impl FheUint { { assert!(module.n().is_multiple_of(T::BITS as usize)); assert_eq!(self.n(), module.n() as u32); - assert_eq!(sk.n(), module.n() as u32); + assert_eq!(sk_glwe.n(), module.n() as u32); } let pt_infos = GLWEPlaintextLayout { @@ -171,7 +171,7 @@ impl FheUint { let (mut pt, scratch_1) = scratch.take_glwe_plaintext(&pt_infos); - self.bits.decrypt(module, &mut pt, sk, scratch_1); + self.bits.decrypt(module, &mut pt, sk_glwe, scratch_1); let mut data_bits: Vec = vec![0i64; module.n()]; pt.decode_vec_i64(&mut data_bits, TorusPrecision(2)); diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/key.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/key.rs index 66061f9..e4c2cf9 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/key.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/key.rs @@ -34,22 +34,22 @@ pub trait BDDKeyInfos { #[derive(Debug, Clone, Copy)] pub struct BDDKeyLayout { - pub cbt: CircuitBootstrappingKeyLayout, - pub ks_glwe: Option, - pub ks_lwe: GLWEToLWEKeyLayout, + pub cbt_layout: CircuitBootstrappingKeyLayout, + pub ks_glwe_layout: Option, + pub ks_lwe_layout: GLWEToLWEKeyLayout, } impl BDDKeyInfos for BDDKeyLayout { fn cbt_infos(&self) -> CircuitBootstrappingKeyLayout { - self.cbt + self.cbt_layout } fn ks_glwe_infos(&self) -> Option { - self.ks_glwe + self.ks_glwe_layout } fn ks_lwe_infos(&self) -> GLWEToLWEKeyLayout { - self.ks_lwe + self.ks_lwe_layout } } @@ -176,9 +176,9 @@ where impl BDDKeyInfos for BDDKeyPrepared { fn cbt_infos(&self) -> CircuitBootstrappingKeyLayout { CircuitBootstrappingKeyLayout { - layout_brk: self.cbt.brk_infos(), - layout_atk: self.cbt.atk_infos(), - layout_tsk: self.cbt.tsk_infos(), + brk_layout: self.cbt.brk_infos(), + atk_layout: self.cbt.atk_infos(), + tsk_layout: self.cbt.tsk_infos(), } } fn ks_glwe_infos(&self) -> Option { diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/glwe_blind_selection.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/glwe_blind_selection.rs index c7a2724..0803043 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/glwe_blind_selection.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/glwe_blind_selection.rs @@ -16,7 +16,7 @@ use rand::RngCore; use crate::bin_fhe::{ bdd_arithmetic::{ - FheUintPrepared, GLWEBlinSelection, + FheUintPrepared, GLWEBlindSelection, tests::test_suite::{TEST_FHEUINT_BASE2K, TEST_RANK, TestContext}, }, blind_rotation::BlindRotationAlgo, @@ -28,7 +28,7 @@ where + GLWESecretPreparedFactory + GGSWPreparedFactory + GGSWEncryptSk - + GLWEBlinSelection + + GLWEBlindSelection + GLWEDecrypt + GLWEEncryptSk, ScratchOwned: ScratchOwnedAlloc + ScratchOwnedBorrow, diff --git a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/mod.rs b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/mod.rs index d9f8b2c..4310c49 100644 --- a/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/mod.rs +++ b/poulpy-schemes/src/bin_fhe/bdd_arithmetic/tests/test_suite/mod.rs @@ -165,8 +165,8 @@ pub(crate) static TEST_GGSW_INFOS: GGSWLayout = GGSWLayout { }; pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout { - cbt: CircuitBootstrappingKeyLayout { - layout_brk: BlindRotationKeyLayout { + cbt_layout: CircuitBootstrappingKeyLayout { + brk_layout: BlindRotationKeyLayout { n_glwe: Degree(TEST_N_GLWE), n_lwe: Degree(TEST_N_LWE), base2k: Base2K(TEST_BRK_BASE2K), @@ -174,7 +174,7 @@ pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout { dnum: Dnum(4), rank: Rank(TEST_RANK), }, - layout_atk: GLWEAutomorphismKeyLayout { + atk_layout: GLWEAutomorphismKeyLayout { n: Degree(TEST_N_GLWE), base2k: Base2K(TEST_ATK_BASE2K), k: TorusPrecision(52), @@ -182,7 +182,7 @@ pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout { dnum: Dnum(4), dsize: Dsize(1), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: Degree(TEST_N_GLWE), base2k: Base2K(TEST_TSK_BASE2K), k: TorusPrecision(52), @@ -191,7 +191,7 @@ pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout { dsize: Dsize(1), }, }, - ks_glwe: Some(GLWESwitchingKeyLayout { + ks_glwe_layout: Some(GLWESwitchingKeyLayout { n: Degree(TEST_N_GLWE), base2k: Base2K(TEST_LWE_BASE2K), k: TorusPrecision(20), @@ -200,7 +200,7 @@ pub(crate) static TEST_BDD_KEY_LAYOUT: BDDKeyLayout = BDDKeyLayout { dnum: Dnum(3), dsize: Dsize(1), }), - ks_lwe: GLWEToLWEKeyLayout { + ks_lwe_layout: GLWEToLWEKeyLayout { n: Degree(TEST_N_GLWE), base2k: Base2K(TEST_LWE_BASE2K), k: TorusPrecision(16), diff --git a/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/key.rs b/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/key.rs index bfc95e5..ae64b8c 100644 --- a/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/key.rs +++ b/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/key.rs @@ -27,9 +27,9 @@ pub trait CircuitBootstrappingKeyInfos { #[derive(Debug, Clone, Copy)] pub struct CircuitBootstrappingKeyLayout { - pub layout_brk: BlindRotationKeyLayout, - pub layout_atk: GLWEAutomorphismKeyLayout, - pub layout_tsk: GGLWEToGGSWKeyLayout, + pub brk_layout: BlindRotationKeyLayout, + pub atk_layout: GLWEAutomorphismKeyLayout, + pub tsk_layout: GGLWEToGGSWKeyLayout, } impl CircuitBootstrappingKeyInfos for CircuitBootstrappingKeyLayout { @@ -38,15 +38,15 @@ impl CircuitBootstrappingKeyInfos for CircuitBootstrappingKeyLayout { } fn atk_infos(&self) -> GLWEAutomorphismKeyLayout { - self.layout_atk + self.atk_layout } fn brk_infos(&self) -> BlindRotationKeyLayout { - self.layout_brk + self.brk_layout } fn tsk_infos(&self) -> GGLWEToGGSWKeyLayout { - self.layout_tsk + self.tsk_layout } } diff --git a/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/tests/circuit_bootstrapping.rs b/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/tests/circuit_bootstrapping.rs index bc529cc..665d13f 100644 --- a/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/tests/circuit_bootstrapping.rs +++ b/poulpy-schemes/src/bin_fhe/circuit_bootstrapping/tests/circuit_bootstrapping.rs @@ -78,7 +78,7 @@ where }; let cbt_infos: CircuitBootstrappingKeyLayout = CircuitBootstrappingKeyLayout { - layout_brk: BlindRotationKeyLayout { + brk_layout: BlindRotationKeyLayout { n_glwe: n_glwe.into(), n_lwe: n_lwe.into(), base2k: base2k_brk.into(), @@ -86,7 +86,7 @@ where dnum: rows_brk.into(), rank: rank.into(), }, - layout_atk: GLWEAutomorphismKeyLayout { + atk_layout: GLWEAutomorphismKeyLayout { n: n_glwe.into(), base2k: base2k_atk.into(), k: k_atk.into(), @@ -94,7 +94,7 @@ where rank: rank.into(), dsize: Dsize(1), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: n_glwe.into(), base2k: base2k_tsk.into(), k: k_tsk.into(), @@ -285,7 +285,7 @@ where }; let cbt_infos: CircuitBootstrappingKeyLayout = CircuitBootstrappingKeyLayout { - layout_brk: BlindRotationKeyLayout { + brk_layout: BlindRotationKeyLayout { n_glwe: n_glwe.into(), n_lwe: n_lwe.into(), base2k: base2k_brk.into(), @@ -293,7 +293,7 @@ where dnum: rows_brk.into(), rank: rank.into(), }, - layout_atk: GLWEAutomorphismKeyLayout { + atk_layout: GLWEAutomorphismKeyLayout { n: n_glwe.into(), base2k: base2k_atk.into(), k: k_atk.into(), @@ -301,7 +301,7 @@ where rank: rank.into(), dsize: Dsize(1), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: n_glwe.into(), base2k: base2k_tsk.into(), k: k_tsk.into(),