use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::inspiring::packing_online;
use crate::math::Poly;
use crate::params::InspireVariant;
use crate::rgsw::external_product;
use crate::rlwe::RlweCiphertext;
use super::error::{pir_err, Result};
#[cfg(feature = "server")]
use super::mmap::MmapDatabase;
use super::query::{ClientQuery, PackingMode, SeededClientQuery};
use super::setup::{EncodedDatabase, ServerCrs};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ServerResponse {
pub ciphertext: RlweCiphertext,
pub column_ciphertexts: Vec<RlweCiphertext>,
}
impl ServerResponse {
pub fn to_binary(&self) -> Result<Vec<u8>> {
bincode::serialize(self).map_err(|e| pir_err!("bincode serialize failed: {}", e))
}
pub fn from_binary(bytes: &[u8]) -> Result<Self> {
bincode::deserialize(bytes).map_err(|e| pir_err!("bincode deserialize failed: {}", e))
}
}
pub fn respond(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
let delta = crs.params.delta();
let shard = encoded_db
.shards
.iter()
.find(|s| s.id == query.shard_id)
.ok_or_else(|| pir_err!("Shard {} not found", query.shard_id))?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let combined = if column_ciphertexts.len() == 1 {
column_ciphertexts[0].clone()
} else {
column_ciphertexts
.iter()
.skip(1)
.fold(column_ciphertexts[0].clone(), |acc, ct| acc.add(ct))
};
Ok(ServerResponse {
ciphertext: combined,
column_ciphertexts,
})
}
pub fn respond_with_variant(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &ClientQuery,
variant: InspireVariant,
) -> Result<ServerResponse> {
match variant {
InspireVariant::NoPacking => respond(crs, encoded_db, query),
InspireVariant::OnePacking | InspireVariant::TwoPacking => match query.packing_mode {
PackingMode::Inspiring => {
if query.inspiring_packing_keys.is_none() {
return Err(pir_err!(
"InspiRING packing keys missing (set packing_mode=tree to use tree packing)"
));
}
respond_inspiring(crs, encoded_db, query)
}
PackingMode::Tree => respond_one_packing(crs, encoded_db, query),
},
}
}
pub fn respond_seeded_with_variant(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &SeededClientQuery,
variant: InspireVariant,
) -> Result<ServerResponse> {
match variant {
InspireVariant::NoPacking => respond_seeded(crs, encoded_db, query),
InspireVariant::OnePacking | InspireVariant::TwoPacking => match query.packing_mode {
PackingMode::Inspiring => {
if query.inspiring_packing_keys.is_none() {
return Err(pir_err!(
"InspiRING packing keys missing (set packing_mode=tree to use tree packing)"
));
}
respond_seeded_inspiring(crs, encoded_db, query)
}
PackingMode::Tree => respond_seeded_packed(crs, encoded_db, query),
},
}
}
pub fn respond_one_packing(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
use crate::inspiring::automorph_pack::pack_lwes;
let _d = crs.ring_dim();
let _q = crs.modulus();
let delta = crs.params.delta();
let shard = encoded_db
.shards
.iter()
.find(|s| s.id == query.shard_id)
.ok_or_else(|| pir_err!("Shard {} not found", query.shard_id))?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let lwe_cts: Vec<_> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.sample_extract_coeff0())
.collect();
let packed = pack_lwes(&lwe_cts, &crs.galois_keys, &crs.params);
Ok(ServerResponse {
ciphertext: packed,
column_ciphertexts: vec![], })
}
pub fn respond_inspiring(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
use crate::inspiring::{generate_rotations, packing_offline, OfflinePackingKeys, PackParams};
let d = crs.ring_dim();
let delta = crs.params.delta();
let ctx = crs.params.ntt_context();
let client_packing_keys = query
.inspiring_packing_keys
.as_ref()
.ok_or_else(|| pir_err!("InspiRING client packing keys missing from query"))?;
let shard = encoded_db
.shards
.iter()
.find(|s| s.id == query.shard_id)
.ok_or_else(|| pir_err!("Shard {} not found", query.shard_id))?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let lwe_cts: Vec<_> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.sample_extract_coeff0())
.collect();
let num_columns = lwe_cts.len();
if num_columns == 0 {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero,
column_ciphertexts: vec![],
});
}
let a_ct_tilde: Vec<Poly> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.a.clone())
.collect();
let mut b_coeffs = vec![0u64; d];
for (i, lwe) in lwe_cts.iter().enumerate() {
if i < d {
b_coeffs[i] = lwe.b;
}
}
let b_poly = Poly::from_coeffs_moduli(b_coeffs, crs.params.moduli());
let pack_params = PackParams::new(&crs.params, num_columns);
let offline_keys = OfflinePackingKeys::generate(&pack_params, crs.inspiring_w_seed);
let precomp = packing_offline(&pack_params, &offline_keys, &a_ct_tilde, &ctx);
let derived_y_all = if client_packing_keys.y_all.is_empty() {
if client_packing_keys.y_body.is_empty() {
return Err(pir_err!(
"InspiRING packing keys invalid: y_all and y_body are both empty"
));
}
Some(generate_rotations(
&pack_params,
&client_packing_keys.y_body,
))
} else {
None
};
let y_all: &[Vec<Poly>] = derived_y_all
.as_deref()
.unwrap_or(&client_packing_keys.y_all);
let packed = packing_online(&precomp, y_all, &b_poly, &ctx);
Ok(ServerResponse {
ciphertext: packed,
column_ciphertexts: vec![],
})
}
pub fn respond_seeded_inspiring(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &SeededClientQuery,
) -> Result<ServerResponse> {
let expanded = query.expand();
respond_inspiring(crs, encoded_db, &expanded)
}
pub fn respond_seeded(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &SeededClientQuery,
) -> Result<ServerResponse> {
let expanded = query.expand();
respond(crs, encoded_db, &expanded)
}
pub fn respond_seeded_packed(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &SeededClientQuery,
) -> Result<ServerResponse> {
let expanded = query.expand();
respond_one_packing(crs, encoded_db, &expanded)
}
pub fn respond_sequential(
crs: &ServerCrs,
encoded_db: &EncodedDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
let _d = crs.ring_dim();
let _q = crs.modulus();
let delta = crs.params.delta();
let ctx = crs.params.ntt_context();
let shard = encoded_db
.shards
.iter()
.find(|s| s.id == query.shard_id)
.ok_or_else(|| pir_err!("Shard {} not found", query.shard_id))?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let mut column_ciphertexts = Vec::with_capacity(shard.polynomials.len());
for db_poly in &shard.polynomials {
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
let rotated = external_product(&rlwe_db, &query.rgsw_ciphertext, &ctx);
column_ciphertexts.push(rotated);
}
let combined = if column_ciphertexts.len() == 1 {
column_ciphertexts[0].clone()
} else {
column_ciphertexts
.iter()
.skip(1)
.fold(column_ciphertexts[0].clone(), |acc, ct| acc.add(ct))
};
Ok(ServerResponse {
ciphertext: combined,
column_ciphertexts,
})
}
#[cfg(feature = "server")]
pub fn respond_mmap(
crs: &ServerCrs,
mmap_db: &MmapDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
let _d = crs.ring_dim();
let _q = crs.modulus();
let delta = crs.params.delta();
let shard = mmap_db.get_shard(query.shard_id)?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let combined = if column_ciphertexts.len() == 1 {
column_ciphertexts[0].clone()
} else {
column_ciphertexts
.iter()
.skip(1)
.fold(column_ciphertexts[0].clone(), |acc, ct| acc.add(ct))
};
Ok(ServerResponse {
ciphertext: combined,
column_ciphertexts,
})
}
#[cfg(feature = "server")]
pub fn respond_mmap_one_packing(
crs: &ServerCrs,
mmap_db: &MmapDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
use crate::inspiring::automorph_pack::pack_lwes;
let _d = crs.ring_dim();
let _q = crs.modulus();
let delta = crs.params.delta();
let shard = mmap_db.get_shard(query.shard_id)?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero,
column_ciphertexts: vec![],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let lwe_cts: Vec<_> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.sample_extract_coeff0())
.collect();
let packed = pack_lwes(&lwe_cts, &crs.galois_keys, &crs.params);
Ok(ServerResponse {
ciphertext: packed,
column_ciphertexts: vec![],
})
}
#[cfg(feature = "server")]
pub fn respond_mmap_inspiring(
crs: &ServerCrs,
mmap_db: &MmapDatabase,
query: &ClientQuery,
) -> Result<ServerResponse> {
use crate::inspiring::{generate_rotations, packing_offline, OfflinePackingKeys, PackParams};
let d = crs.ring_dim();
let delta = crs.params.delta();
let ctx = crs.params.ntt_context();
let client_packing_keys = query
.inspiring_packing_keys
.as_ref()
.ok_or_else(|| pir_err!("InspiRING client packing keys missing from query"))?;
let shard = mmap_db.get_shard(query.shard_id)?;
if shard.polynomials.is_empty() {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero.clone(),
column_ciphertexts: vec![zero],
});
}
let column_ciphertexts: Vec<RlweCiphertext> = shard
.polynomials
.par_iter()
.map(|db_poly| {
let local_ctx = crs.params.ntt_context();
let rlwe_db = RlweCiphertext::trivial_encrypt(db_poly, delta, &crs.params);
external_product(&rlwe_db, &query.rgsw_ciphertext, &local_ctx)
})
.collect();
let lwe_cts: Vec<_> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.sample_extract_coeff0())
.collect();
let num_columns = lwe_cts.len();
if num_columns == 0 {
let zero = RlweCiphertext::zero(&crs.params);
return Ok(ServerResponse {
ciphertext: zero,
column_ciphertexts: vec![],
});
}
let a_ct_tilde: Vec<Poly> = column_ciphertexts
.iter()
.map(|rlwe| rlwe.a.clone())
.collect();
let mut b_coeffs = vec![0u64; d];
for (i, lwe) in lwe_cts.iter().enumerate() {
if i < d {
b_coeffs[i] = lwe.b;
}
}
let b_poly = Poly::from_coeffs_moduli(b_coeffs, crs.params.moduli());
let pack_params = PackParams::new(&crs.params, num_columns);
let offline_keys = OfflinePackingKeys::generate(&pack_params, crs.inspiring_w_seed);
let precomp = packing_offline(&pack_params, &offline_keys, &a_ct_tilde, &ctx);
let derived_y_all = if client_packing_keys.y_all.is_empty() {
if client_packing_keys.y_body.is_empty() {
return Err(pir_err!(
"InspiRING packing keys invalid: y_all and y_body are both empty"
));
}
Some(generate_rotations(
&pack_params,
&client_packing_keys.y_body,
))
} else {
None
};
let y_all: &[Vec<Poly>] = derived_y_all
.as_deref()
.unwrap_or(&client_packing_keys.y_all);
let packed = packing_online(&precomp, y_all, &b_poly, &ctx);
Ok(ServerResponse {
ciphertext: packed,
column_ciphertexts: vec![],
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::{GaussianSampler, Poly};
use crate::pir::query::query;
use crate::pir::setup::setup;
fn test_params() -> crate::params::InspireParams {
crate::params::InspireParams {
ring_dim: 256,
q: 1152921504606830593,
crt_moduli: vec![1152921504606830593],
p: 65536,
sigma: 6.4,
gadget_base: 1 << 20,
gadget_len: 3,
security_level: crate::params::SecurityLevel::Bits128,
}
}
#[test]
fn test_respond_produces_valid_ciphertext() {
let params = test_params();
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 32;
let num_entries = params.ring_dim;
let database: Vec<u8> = (0..(num_entries * entry_size))
.map(|i| (i % 256) as u8)
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
let target_index = 42u64;
let (_state, client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response = respond(&crs, &encoded_db, &client_query);
assert!(response.is_ok());
let response = response.unwrap();
assert_eq!(response.ciphertext.ring_dim(), params.ring_dim);
}
#[test]
fn test_respond_invalid_shard() {
let params = test_params();
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 32;
let num_entries = params.ring_dim;
let database: Vec<u8> = (0..(num_entries * entry_size))
.map(|i| (i % 256) as u8)
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
let target_index = 0u64;
let (_, mut client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
client_query.shard_id = 999;
let response = respond(&crs, &encoded_db, &client_query);
assert!(response.is_err());
}
#[test]
fn test_ciphertext_addition() {
let params = test_params();
let d = params.ring_dim;
let moduli = params.moduli();
let a1 = Poly::zero_moduli(d, moduli);
let mut b1_coeffs = vec![0u64; d];
b1_coeffs[0] = 100;
let b1 = Poly::from_coeffs_moduli(b1_coeffs, moduli);
let ct1 = RlweCiphertext::from_parts(a1, b1);
let a2 = Poly::zero_moduli(d, moduli);
let mut b2_coeffs = vec![0u64; d];
b2_coeffs[0] = 200;
let b2 = Poly::from_coeffs_moduli(b2_coeffs, moduli);
let ct2 = RlweCiphertext::from_parts(a2, b2);
let combined = ct1.add(&ct2);
assert_eq!(combined.b.coeff(0), 300);
}
#[cfg(feature = "server")]
#[test]
fn test_respond_mmap() {
use crate::pir::save_shards_binary;
use tempfile::tempdir;
let params = test_params();
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 32;
let num_entries = params.ring_dim;
let database: Vec<u8> = (0..(num_entries * entry_size))
.map(|i| (i % 256) as u8)
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
let dir = tempdir().unwrap();
save_shards_binary(&encoded_db.shards, dir.path()).unwrap();
let mmap_db = MmapDatabase::open(dir.path(), encoded_db.config.clone()).unwrap();
let target_index = 42u64;
let (_state, client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response_inmem = respond(&crs, &encoded_db, &client_query).unwrap();
let response_mmap = respond_mmap(&crs, &mmap_db, &client_query).unwrap();
assert_eq!(
response_inmem.ciphertext.ring_dim(),
response_mmap.ciphertext.ring_dim()
);
assert_eq!(
response_inmem.column_ciphertexts.len(),
response_mmap.column_ciphertexts.len()
);
}
#[cfg(feature = "server")]
#[test]
fn test_respond_mmap_inspiring() {
use crate::pir::{extract_inspiring, save_shards_binary};
use tempfile::tempdir;
let params = test_params();
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 2; let num_entries = params.ring_dim;
let database: Vec<u8> = (0..num_entries)
.flat_map(|i| {
let low_byte = (i % 256) as u8;
let high_byte = 0u8;
vec![low_byte, high_byte]
})
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
let dir = tempdir().unwrap();
save_shards_binary(&encoded_db.shards, dir.path()).unwrap();
let mmap_db = MmapDatabase::open(dir.path(), encoded_db.config.clone()).unwrap();
let target_index = 42u64;
let (state, client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response_inmem = respond_inspiring(&crs, &encoded_db, &client_query).unwrap();
let response_mmap = respond_mmap_inspiring(&crs, &mmap_db, &client_query).unwrap();
let extracted_inmem = extract_inspiring(&crs, &state, &response_inmem, entry_size).unwrap();
let extracted_mmap = extract_inspiring(&crs, &state, &response_mmap, entry_size).unwrap();
let expected_start = (target_index as usize) * entry_size;
let expected = &database[expected_start..expected_start + entry_size];
assert_eq!(extracted_inmem.as_slice(), expected);
assert_eq!(extracted_mmap.as_slice(), expected);
}
#[test]
fn test_respond_one_packing_correctness() {
use crate::params::InspireVariant;
use crate::pir::extract_with_variant;
let params = test_params();
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 64;
let num_entries = params.ring_dim;
let database: Vec<u8> = (0..(num_entries * entry_size))
.map(|i| (i % 256) as u8)
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
for target_index in [0u64, 1, 42] {
let (state, client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response_no_pack = respond(&crs, &encoded_db, &client_query).unwrap();
let extracted_no_pack =
crate::pir::extract(&crs, &state, &response_no_pack, entry_size).unwrap();
let expected_start = (target_index as usize) * entry_size;
let expected = &database[expected_start..expected_start + entry_size];
assert_eq!(
extracted_no_pack.as_slice(),
expected,
"NoPacking should work for index {}",
target_index
);
let response_one_pack = respond_one_packing(&crs, &encoded_db, &client_query).unwrap();
let extracted_one_pack = extract_with_variant(
&crs,
&state,
&response_one_pack,
entry_size,
InspireVariant::OnePacking,
)
.unwrap();
assert_eq!(
extracted_one_pack.len(),
entry_size,
"OnePacking should produce correct size for index {}",
target_index
);
}
}
#[test]
fn test_respond_one_packing_small_values() {
use crate::params::InspireVariant;
use crate::pir::extract_with_variant;
let params = test_params();
let d = params.ring_dim;
let mut sampler = GaussianSampler::new(params.sigma);
let entry_size = 2; let num_entries = d;
let database: Vec<u8> = (0..num_entries)
.flat_map(|i| {
let low_byte = (i % 256) as u8;
let high_byte = 0u8; vec![low_byte, high_byte]
})
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
for target_index in [0u64, 1, 42, 100] {
let (state, client_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response_no_pack = respond(&crs, &encoded_db, &client_query).unwrap();
let extracted_no_pack =
crate::pir::extract(&crs, &state, &response_no_pack, entry_size).unwrap();
let response_one_pack = respond_one_packing(&crs, &encoded_db, &client_query).unwrap();
let extracted_one_pack = extract_with_variant(
&crs,
&state,
&response_one_pack,
entry_size,
InspireVariant::OnePacking,
)
.unwrap();
let expected_start = (target_index as usize) * entry_size;
let expected = &database[expected_start..expected_start + entry_size];
assert_eq!(
extracted_no_pack.as_slice(),
expected,
"NoPacking should work for index {}",
target_index
);
assert_eq!(
extracted_one_pack.as_slice(),
expected,
"OnePacking should work with small values for index {}",
target_index
);
}
}
#[test]
fn test_inspire_sizes_production() {
use crate::pir::query::query_seeded;
let params = crate::params::InspireParams {
ring_dim: 2048,
q: 1152921504606830593,
crt_moduli: vec![1152921504606830593],
p: 65536,
sigma: 6.4,
gadget_base: 1 << 20,
gadget_len: 3,
security_level: crate::params::SecurityLevel::Bits128,
};
let d = params.ring_dim;
let entry_size = 32; let mut sampler = GaussianSampler::new(params.sigma);
let num_entries = d;
let database: Vec<u8> = (0..(num_entries * entry_size))
.map(|i| (i % 256) as u8)
.collect();
let (crs, encoded_db, rlwe_sk) =
setup(¶ms, &database, entry_size, &mut sampler).unwrap();
let target_index = 42u64;
let (_state, full_query) = query(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let (_state, seeded_query) = query_seeded(
&crs,
target_index,
&encoded_db.config,
&rlwe_sk,
&mut sampler,
)
.unwrap();
let response_no_pack = respond(&crs, &encoded_db, &full_query).unwrap();
let response_one_pack = respond_one_packing(&crs, &encoded_db, &full_query).unwrap();
let query_full_bytes = bincode::serialize(&full_query).unwrap();
let query_seeded_bytes = bincode::serialize(&seeded_query).unwrap();
let resp_0_bytes = response_no_pack.to_binary().unwrap();
let resp_1_bytes = response_one_pack.to_binary().unwrap();
println!();
println!("╔══════════════════════════════════════════════════════════════╗");
println!(
"║ InsPIRe Size Comparison (d={}, entry={}B, 16 columns) ║",
d, entry_size
);
println!("╠══════════════════════════════════════════════════════════════╣");
println!("║ QUERY SIZES ║");
println!("╟──────────────────────────────────────────────────────────────╢");
println!(
"║ Full query: {:>8} bytes ({:>6.1} KB) ║",
query_full_bytes.len(),
query_full_bytes.len() as f64 / 1024.0
);
println!(
"║ Seeded query: {:>8} bytes ({:>6.1} KB) [{:.0}% of full] ║",
query_seeded_bytes.len(),
query_seeded_bytes.len() as f64 / 1024.0,
query_seeded_bytes.len() as f64 / query_full_bytes.len() as f64 * 100.0
);
println!("╠══════════════════════════════════════════════════════════════╣");
println!("║ RESPONSE SIZES ║");
println!("╟──────────────────────────────────────────────────────────────╢");
println!(
"║ NoPacking (^0): {:>8} bytes ({:>6.1} KB) ║",
resp_0_bytes.len(),
resp_0_bytes.len() as f64 / 1024.0
);
println!(
"║ OnePacking (^1): {:>8} bytes ({:>6.1} KB) [{:.1}x smaller] ║",
resp_1_bytes.len(),
resp_1_bytes.len() as f64 / 1024.0,
resp_0_bytes.len() as f64 / resp_1_bytes.len() as f64
);
println!("╠══════════════════════════════════════════════════════════════╣");
println!("║ TOTAL ROUNDTRIP (Query + Response) ║");
println!("╟──────────────────────────────────────────────────────────────╢");
let total_0 = query_full_bytes.len() + resp_0_bytes.len();
let total_1 = query_full_bytes.len() + resp_1_bytes.len();
let total_2 = query_seeded_bytes.len() + resp_1_bytes.len();
println!(
"║ InsPIRe^0 (full+nopack): {:>8} bytes ({:>6.1} KB) ║",
total_0,
total_0 as f64 / 1024.0
);
println!(
"║ InsPIRe^1 (full+packed): {:>8} bytes ({:>6.1} KB) ║",
total_1,
total_1 as f64 / 1024.0
);
println!(
"║ InsPIRe^2 (seeded+packed): {:>8} bytes ({:>6.1} KB) ║",
total_2,
total_2 as f64 / 1024.0
);
println!("╠══════════════════════════════════════════════════════════════╣");
println!("║ BANDWIDTH SAVINGS vs InsPIRe^0 ║");
println!("╟──────────────────────────────────────────────────────────────╢");
println!(
"║ InsPIRe^1: {:.1}x reduction ║",
total_0 as f64 / total_1 as f64
);
println!(
"║ InsPIRe^2: {:.1}x reduction ║",
total_0 as f64 / total_2 as f64
);
println!("╚══════════════════════════════════════════════════════════════╝");
}
}