Examples, benchmarks, and minor changes for consistency, in BDD API
jump to
@@ -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
@@ -9,7 +9,7 @@
## 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
@@ -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<BE: Backend, BRA: BlindRotationAlgo> { + module: Module<BE>, + scratch: ScratchOwned<BE>, + a_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE>, + b_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE>, + bdd_key_prepared: BDDKeyPrepared<Vec<u8>, BRA, BE>, + glwe_layout: GLWELayout, +} + +struct Params { + name: String, + block_size: usize, + glwe_layout: GLWELayout, + ggsw_layout: GGSWLayout, + bdd_layout: BDDKeyLayout, +} + +fn setup_benchmark<BE: Backend, BRA: BlindRotationAlgo>(params: &Params) -> BenchmarkSetup<BE, BRA> +where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + CircuitBootstrappingKeyEncryptSk<BRA, BE> + + CircuitBootstrappingKeyPreparedFactory<BRA, BE> + + CirtuitBootstrappingExecute<BRA, BE> + + GGSWPreparedFactory<BE> + + GGSWNoise<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, +{ + // Scratch space (16MB) + let mut scratch: ScratchOwned<BE> = 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<BE> = Module::<BE>::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<Vec<u8>> = LWESecret::alloc(n_lwe); + sk_lwe.fill_binary_block(params.block_size, &mut source_xs); + + let mut sk_glwe: GLWESecret<Vec<u8>> = GLWESecret::alloc(n_glwe, rank); + sk_glwe.fill_ternary_prob(0.5, &mut source_xs); + + // Circuit bootstrapping evaluation key + let mut cbt_key: CircuitBootstrappingKey<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<BE: Backend, BRA: BlindRotationAlgo, F>(setup: BenchmarkSetup<BE, BRA>, operation: F) -> impl FnMut() +where + Module<BE>: ExecuteBDDCircuit2WTo1W<BE>, + Scratch<BE>: ScratchTakeCore<BE>, + ScratchOwned<BE>: ScratchOwnedBorrow<BE>, + F: Fn( + &mut FheUint<Vec<u8>, u32>, + &Module<BE>, + &FheUintPrepared<Vec<u8>, u32, BE>, + &FheUintPrepared<Vec<u8>, u32, BE>, + &BDDKeyPrepared<Vec<u8>, BRA, BE>, + &mut Scratch<BE>, + ), +{ + let BenchmarkSetup { + module, + mut scratch, + a_enc_prepared, + b_enc_prepared, + bdd_key_prepared, + glwe_layout, + } = setup; + + let mut c_enc: FheUint<Vec<u8>, 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<BE: Backend, BRA: BlindRotationAlgo, F>( + group: &mut criterion::BenchmarkGroup<'_, criterion::measurement::WallTime>, + params: &Params, + operation_name: &str, + operation: F, +) where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + CircuitBootstrappingKeyEncryptSk<BRA, BE> + + CircuitBootstrappingKeyPreparedFactory<BRA, BE> + + CirtuitBootstrappingExecute<BRA, BE> + + GGSWPreparedFactory<BE> + + GGSWNoise<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, + F: Fn( + &mut FheUint<Vec<u8>, u32>, + &Module<BE>, + &FheUintPrepared<Vec<u8>, u32, BE>, + &FheUintPrepared<Vec<u8>, u32, BE>, + &BDDKeyPrepared<Vec<u8>, BRA, BE>, + &mut Scratch<BE>, + ) + 'static, +{ + let setup = setup_benchmark::<BE, BRA>(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<BE: Backend, BRA: BlindRotationAlgo>(c: &mut Criterion, label: &str) +where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + CircuitBootstrappingKeyEncryptSk<BRA, BE> + + CircuitBootstrappingKeyPreparedFactory<BRA, BE> + + CirtuitBootstrappingExecute<BRA, BE> + + GGSWPreparedFactory<BE> + + GGSWNoise<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, +{ + 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::<BE, BRA, _>( + &mut group, + ¶ms, + "add", + |c_enc, module, a, b, key, scratch| { + c_enc.add(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "sub", + |c_enc, module, a, b, key, scratch| { + c_enc.sub(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "sll", + |c_enc, module, a, b, key, scratch| { + c_enc.sll(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "sra", + |c_enc, module, a, b, key, scratch| { + c_enc.sra(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "srl", + |c_enc, module, a, b, key, scratch| { + c_enc.srl(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "slt", + |c_enc, module, a, b, key, scratch| { + c_enc.slt(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "sltu", + |c_enc, module, a, b, key, scratch| { + c_enc.sltu(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "or", + |c_enc, module, a, b, key, scratch| { + c_enc.or(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &mut group, + ¶ms, + "and", + |c_enc, module, a, b, key, scratch| { + c_enc.and(module, a, b, key, scratch); + }, + ); + + bench_operation::<BE, BRA, _>( + &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::<BackendImpl, CGGI>(c, label); +} + +criterion_group!(benches, bench_bdd_arithmetic_fft64); +criterion_main!(benches);
@@ -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<BE: Backend, BRA: BlindRotationAlgo>(c: &mut Criterion, label: &str) +where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + CircuitBootstrappingKeyEncryptSk<BRA, BE> + + CircuitBootstrappingKeyPreparedFactory<BRA, BE> + + CirtuitBootstrappingExecute<BRA, BE> + + GGSWPreparedFactory<BE> + + GGSWNoise<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, +{ + 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<BE: Backend, BRA: BlindRotationAlgo>(params: &Params) -> impl FnMut() + where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + CircuitBootstrappingKeyEncryptSk<BRA, BE> + + CircuitBootstrappingKeyPreparedFactory<BRA, BE> + + CirtuitBootstrappingExecute<BRA, BE> + + GGSWPreparedFactory<BE> + + GGSWNoise<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, /* TODO find a way to remove this bound or move it to CBT KEY */ + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, + { + // Scratch space (4MB) + let mut scratch: ScratchOwned<BE> = 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<BE> = Module::<BE>::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<Vec<u8>> = LWESecret::alloc(n_lwe); + sk_lwe.fill_binary_block(params.block_size, &mut source_xs); + + let mut sk_glwe: GLWESecret<Vec<u8>> = 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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::<BE, BRA>(¶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::<BackendImpl, CGGI>(c, label); +} + +criterion_group!(benches, bench_bdd_prepare_fft64); +criterion_main!(benches);
@@ -85,9 +85,9 @@ {
// Scratch space (4MB) let mut scratch: ScratchOwned<BE> = 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<BE> = Module::<BE>::new(n_glwe.as_u32() as u64);@@ -97,7 +97,6 @@ let mut source_xe: Source = Source::new([1u8; 32]);
let mut sk_lwe: LWESecret<Vec<u8>> = LWESecret::alloc(n_lwe); sk_lwe.fill_binary_block(params.block_size, &mut source_xs); - sk_lwe.fill_zero(); let mut sk_glwe: GLWESecret<Vec<u8>> = GLWESecret::alloc(n_glwe, rank); sk_glwe.fill_ternary_prob(0.5, &mut source_xs);@@ -151,7 +150,7 @@ dsize: 1_u32.into(),
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 @@ k: 52_u32.into(),
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 @@ dnum: 3_u32.into(),
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(),
@@ -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<BE: Backend, BRA: BlindRotationAlgo>() +where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + GGSWPreparedFactory<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE> + + GLWEBlindSelection<u32, BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, +{ + ////////// 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::<BE>::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<BE> = 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<u32> = (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<FheUint<Vec<u8>, u32>> = Vec::new(); + for input in &inputs_a_vec { + let mut next_input: FheUint<Vec<u8>, 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<usize, &mut FheUint<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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::<FFT64Avx, CGGI>(); + + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + example_bdd_arithmetic::<FFT64Ref, CGGI>(); +}
@@ -77,8 +77,8 @@
// 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 @@ k: k_brk.into(),
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 @@ dnum: rows_trace.into(),
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 @@
// LWE secret let mut sk_lwe: LWESecret<Vec<u8>> = 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<Vec<u8>> = 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<Vec<u8>, BackendImpl> = GLWESecretPrepared::alloc(&module, rank.into());@@ -151,7 +151,7 @@
// LWE plaintext let mut pt_lwe: LWEPlaintext<Vec<u8>> = 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 @@
let now: Instant = Instant::now(); // Circuit bootstrapping evaluation key - let mut cbt_key: CircuitBootstrappingKey<Vec<u8>, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_infos); + let mut cbt_key: CircuitBootstrappingKey<Vec<u8>, CGGI> = CircuitBootstrappingKey::alloc_from_infos(&cbt_layout); cbt_key.encrypt_sk( &module,@@ -192,7 +192,7 @@ let mut res: GGSW<Vec<u8>> = GGSW::alloc_from_infos(&ggsw_infos);
// Circuit bootstrapping key prepared (opaque backend dependant write only struct) let mut cbt_prepared: CircuitBootstrappingKeyPrepared<Vec<u8>, 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)
@@ -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<BE: Backend, BRA: BlindRotationAlgo>() +where + Module<BE>: ModuleNew<BE> + + ModuleN + + GLWESecretPreparedFactory<BE> + + GLWEExternalProduct<BE> + + GLWEDecrypt<BE> + + LWEEncryptSk<BE> + + GGSWPreparedFactory<BE> + + GLWEEncryptSk<BE> + + VecZnxRotateInplace<BE> + + BDDKeyEncryptSk<BRA, BE> + + BDDKeyPreparedFactory<BRA, BE> + + FheUintPrepare<BRA, BE> + + ExecuteBDDCircuit2WTo1W<BE> + + GLWEBlindSelection<u32, BE>, + BlindRotationKey<Vec<u8>, BRA>: BlindRotationKeyFactory<BRA>, // TODO find a way to remove this bound or move it to CBT KEY + ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>, + Scratch<BE>: ScratchTakeCore<BE>, +{ + ////////// 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::<BE>::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<BE> = 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<Vec<u8>, 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<u32> = (0..3).map(|_| rng.random_range(0..u32::MAX - 1)).collect(); + + let mut inputs_enc: Vec<FheUint<Vec<u8>, 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<Vec<u8>, BRA, BE> = BDDKeyPrepared::alloc_from_infos(&module, &bdd_layout); + bdd_key_prepared.prepare(&module, &bdd_key, scratch.borrow()); + + let mut max_enc: FheUint<Vec<u8>, 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<Vec<u8>, u32> = FheUint::alloc_from_infos(&glwe_layout); + + // Allocating the intermediate ciphertext c_enc + let mut compare_enc: FheUint<Vec<u8>, u32> = FheUint::alloc_from_infos(&glwe_layout); + let mut compare_enc_prepared: FheUintPrepared<Vec<u8>, u32, BE> = FheUintPrepared::alloc_from_infos(&module, &ggsw_layout); + + for input_i in inputs_enc.iter_mut() { + let mut max_enc_prepared: FheUintPrepared<Vec<u8>, 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<Vec<u8>, 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)]); + + <Module<BE> as GLWEBlindSelection<u32, BE>>::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::<FFT64Avx, CGGI>(); + + #[cfg(not(all(feature = "enable-avx", target_arch = "x86_64")))] + example_max_array::<FFT64Ref, CGGI>(); +}
@@ -188,15 +188,35 @@ }
} }; } + +// 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!(
@@ -8,9 +8,9 @@ use poulpy_hal::layouts::{Backend, Module, Scratch, ZnxZero};
use crate::bin_fhe::bdd_arithmetic::{Cmux, GetGGSWBit, UnsignedInteger}; -impl<T: UnsignedInteger, BE: Backend> GLWEBlinSelection<T, BE> for Module<BE> where Self: GLWECopy + Cmux<BE> + GLWEDecrypt<BE> {} +impl<T: UnsignedInteger, BE: Backend> GLWEBlindSelection<T, BE> for Module<BE> where Self: GLWECopy + Cmux<BE> + GLWEDecrypt<BE> {} -pub trait GLWEBlinSelection<T: UnsignedInteger, BE: Backend> +pub trait GLWEBlindSelection<T: UnsignedInteger, BE: Backend> where Self: GLWECopy + Cmux<BE> + GLWEDecrypt<BE>, {
@@ -87,7 +87,7 @@ pub fn encrypt_sk<S, M, BE: Backend>(
&mut self, module: &M, data: T, - sk: &S, + sk_glwe: &S, source_xa: &mut Source, source_xe: &mut Source, scratch: &mut Scratch<BE>,@@ -100,7 +100,7 @@ #[cfg(debug_assertions)]
{ 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<i64> = vec![0i64; module.n()];@@ -122,7 +122,7 @@ let (mut pt, scratch_1) = scratch.take_glwe_plaintext(&pt_infos);
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 @@ pt.encode_vec_i64(&data_bits, TorusPrecision(2));
self.bits.noise(module, &pt, sk, scratch_1) } - pub fn decrypt<S, M, BE: Backend>(&self, module: &M, sk: &S, scratch: &mut Scratch<BE>) -> T + pub fn decrypt<S, M, BE: Backend>(&self, module: &M, sk_glwe: &S, scratch: &mut Scratch<BE>) -> T where S: GLWESecretPreparedToRef<BE> + GLWEInfos, M: ModuleLogN + GLWEDecrypt<BE>,@@ -160,7 +160,7 @@ #[cfg(debug_assertions)]
{ 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 @@ };
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<i64> = vec![0i64; module.n()]; pt.decode_vec_i64(&mut data_bits, TorusPrecision(2));
@@ -34,22 +34,22 @@ }
#[derive(Debug, Clone, Copy)] pub struct BDDKeyLayout { - pub cbt: CircuitBootstrappingKeyLayout, - pub ks_glwe: Option<GLWESwitchingKeyLayout>, - pub ks_lwe: GLWEToLWEKeyLayout, + pub cbt_layout: CircuitBootstrappingKeyLayout, + pub ks_glwe_layout: Option<GLWESwitchingKeyLayout>, + 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<GLWESwitchingKeyLayout> { - self.ks_glwe + self.ks_glwe_layout } fn ks_lwe_infos(&self) -> GLWEToLWEKeyLayout { - self.ks_lwe + self.ks_lwe_layout } }@@ -176,9 +176,9 @@
impl<D: DataRef, BRA: BlindRotationAlgo, BE: Backend> BDDKeyInfos for BDDKeyPrepared<D, BRA, BE> { 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<GLWESwitchingKeyLayout> {
@@ -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 @@ Module<BE>: ModuleNew<BE>
+ GLWESecretPreparedFactory<BE> + GGSWPreparedFactory<BE> + GGSWEncryptSk<BE> - + GLWEBlinSelection<u32, BE> + + GLWEBlindSelection<u32, BE> + GLWEDecrypt<BE> + GLWEEncryptSk<BE>, ScratchOwned<BE>: ScratchOwnedAlloc<BE> + ScratchOwnedBorrow<BE>,
@@ -165,8 +165,8 @@ dsize: Dsize(1),
}; 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 @@ k: TorusPrecision(52),
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 @@ rank: Rank(TEST_RANK),
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 @@ dnum: Dnum(4),
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 @@ rank_out: Rank(1),
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),
@@ -27,9 +27,9 @@ }
#[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 @@ unimplemented!("unimplemented 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 } }
@@ -78,7 +78,7 @@ base2k: base2k_lwe.into(),
}; 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 @@ k: k_brk.into(),
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 @@ dnum: rows_atk.into(),
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 @@ base2k: base2k_lwe.into(),
}; 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 @@ k: k_brk.into(),
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 @@ dnum: rows_atk.into(),
rank: rank.into(), dsize: Dsize(1), }, - layout_tsk: GGLWEToGGSWKeyLayout { + tsk_layout: GGLWEToGGSWKeyLayout { n: n_glwe.into(), base2k: base2k_tsk.into(), k: k_tsk.into(),