From 1b18df3d5d959782552ee0a521c83b69f640a11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 17 Jun 2024 20:20:25 +0200 Subject: [PATCH 01/42] NDSS submission --- Cargo.toml | 67 +- benches/BENCHMARKS.md | 29 - benches/benches.rs | 127 ---- benches/generate.sh | 12 - examples/.gitignore | 1 - examples/search.rs | 55 -- examples/upsert.rs | 54 -- src/address.rs | 28 + src/dem.rs | 0 src/edx/chain_table.rs | 190 ------ src/edx/entry_table.rs | 206 ------ src/edx/mod.rs | 389 ------------ src/edx/structs.rs | 393 ------------ src/error.rs | 68 +- src/findex_graph/compact.rs | 83 --- src/findex_graph/graph.rs | 167 ----- src/findex_graph/mod.rs | 296 --------- src/findex_graph/structs.rs | 83 --- src/findex_mm/compact.rs | 240 ------- src/findex_mm/mm.rs | 507 --------------- src/findex_mm/mod.rs | 157 ----- src/findex_mm/structs.rs | 222 ------- src/index/mod.rs | 437 ------------- src/index/structs.rs | 240 ------- src/kv.rs | 99 +++ src/lib.rs | 318 +--------- src/macros.rs | 84 --- src/obf.rs | 273 ++++++++ src/parameters.rs | 35 -- src/stm.rs | 24 + tests/non_regression.rs | 160 ----- tests/test_in_memory.rs | 1174 ----------------------------------- 32 files changed, 447 insertions(+), 5771 deletions(-) delete mode 100644 benches/BENCHMARKS.md delete mode 100644 benches/benches.rs delete mode 100644 benches/generate.sh delete mode 100644 examples/.gitignore delete mode 100644 examples/search.rs delete mode 100644 examples/upsert.rs create mode 100644 src/address.rs create mode 100644 src/dem.rs delete mode 100644 src/edx/chain_table.rs delete mode 100644 src/edx/entry_table.rs delete mode 100644 src/edx/mod.rs delete mode 100644 src/edx/structs.rs delete mode 100644 src/findex_graph/compact.rs delete mode 100644 src/findex_graph/graph.rs delete mode 100644 src/findex_graph/mod.rs delete mode 100644 src/findex_graph/structs.rs delete mode 100644 src/findex_mm/compact.rs delete mode 100644 src/findex_mm/mm.rs delete mode 100644 src/findex_mm/mod.rs delete mode 100644 src/findex_mm/structs.rs delete mode 100644 src/index/mod.rs delete mode 100644 src/index/structs.rs create mode 100644 src/kv.rs delete mode 100644 src/macros.rs create mode 100644 src/obf.rs delete mode 100644 src/parameters.rs create mode 100644 src/stm.rs delete mode 100644 tests/non_regression.rs delete mode 100644 tests/test_in_memory.rs diff --git a/Cargo.toml b/Cargo.toml index 73ed88ac..a4c209a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,67 +1,8 @@ [package] -name = "cosmian_findex" -version = "6.0.0" -authors = [ - "Chloé Hébant ", - "Bruno Grieder ", - "Célia Corsin ", - "Emmanuel Coste ", - "Théophile Brézot ", -] -categories = ["cosmian::crypto"] +name = "findex-bis" +version = "0.1.0" edition = "2021" -keywords = ["SSE"] -license-file = "LICENSE.md" -repository = "https://github.com/Cosmian/findex/" -description = "Symmetric Searchable Encryption" - -[lib] -crate-type = ["cdylib", "lib", "staticlib"] -name = "cosmian_findex" -path = "src/lib.rs" - -[features] -in_memory = ["cosmian_crypto_core/ser"] [dependencies] -# Once available in stable Rust (presumably 1.74), use std async fn in trait -# -async-trait = "0.1.74" -base64 = "0.21.5" -cosmian_crypto_core = { version = "9.3.0", default-features = false, features = [ - "aes", - "sha3", -] } -# Once available in stable Rust, use `!` std primitive -# -never = "0.1.0" -tiny-keccak = { version = "2.0.2", features = ["kmac", "sha3"] } -tracing = "0.1" -zeroize = "1.7.0" - -[dev-dependencies] -actix-rt = "2.9.0" -criterion = "0.5.1" -futures = "0.3.29" -rand = "0.8.5" - -[[bench]] -harness = false -name = "benches" -required-features = ["in_memory"] - -[[test]] -name = "test_in_memory" -required-features = ["in_memory"] - -[[test]] -name = "non_regression" -required-features = ["in_memory"] - -[[example]] -name = "search" -required-features = ["in_memory"] - -[[example]] -name = "upsert" -required-features = ["in_memory"] +aes = "0.8.4" +cosmian_crypto_core = "9.4.0" diff --git a/benches/BENCHMARKS.md b/benches/BENCHMARKS.md deleted file mode 100644 index 333f314f..00000000 --- a/benches/BENCHMARKS.md +++ /dev/null @@ -1,29 +0,0 @@ -# Benchmarks - -## Table of Contents - -- [Overview](#overview) -- [Benchmark Results](#benchmark-results) - - [search](#search) - - [upsert](#upsert) - -## Overview - -This is a benchmark comparison report. - -## Benchmark Results - -### search - -| | `Searching 1 keyword(s)` | `Searching 10 keyword(s)` | `Searching 100 keyword(s)` | `Searching 1000 keyword(s)` | -|:-------|:----------------------------------|:-----------------------------------|:------------------------------------|:------------------------------------- | -| | `7.26 us` (✅ **1.00x**) | `60.26 us` (❌ *8.31x slower*) | `597.19 us` (❌ *82.31x slower*) | `6.02 ms` (❌ *829.65x slower*) | - -### upsert - -| | `Upserting 10 keyword(s)` | `Upserting 100 keyword(s)` | `Upserting 1000 keyword(s)` | -|:-------|:-----------------------------------|:------------------------------------|:------------------------------------- | -| | `113.48 us` (✅ **1.00x**) | `1.12 ms` (❌ *9.87x slower*) | `11.51 ms` (❌ *101.42x slower*) | - ---- -Made with [criterion-table](https://github.com/nu11ptr/criterion-table) diff --git a/benches/benches.rs b/benches/benches.rs deleted file mode 100644 index 3edf971b..00000000 --- a/benches/benches.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use cosmian_crypto_core::CsRng; -use cosmian_findex::{ - ChainTable, Data, DxEnc, EntryTable, Findex, InMemoryDb, Index, IndexedValue, - IndexedValueToKeywordsMap, Keyword, Keywords, Label, -}; -use criterion::{criterion_group, criterion_main, Criterion}; -use futures::executor::block_on; -use rand::SeedableRng; - -fn prepare_locations_and_words(number: usize) -> IndexedValueToKeywordsMap { - let mut locations_and_words = HashMap::with_capacity(number); - for idx in 0..number { - let mut words = HashSet::new(); - words.insert(Keyword::from(format!("first_name_{idx}").as_bytes())); - words.insert(Keyword::from(format!("name_{idx}").as_bytes())); - locations_and_words.insert( - IndexedValue::Data(Data::from(idx.to_be_bytes().as_slice())), - Keywords::from(words.clone()), - ); - } - IndexedValueToKeywordsMap::from(locations_and_words) -} - -fn prepare_keywords(number: usize) -> HashSet { - let mut keywords = HashSet::with_capacity(number); - for idx in 0..number { - keywords.insert(Keyword::from(format!("name_{idx}").as_str())); - } - keywords -} - -fn bench_search(c: &mut Criterion) { - // - // Generate new dataset - // - let mut group = c.benchmark_group("search"); - - let mut rng = CsRng::from_entropy(); - let label = Label::random(&mut rng); - let locations_and_words = prepare_locations_and_words(10000); - - // - // Prepare indexes to be search - // - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - block_on(findex.add(&key, &label, locations_and_words)).expect("msg"); - - println!( - "Entry Table length: {}", - findex.findex_graph.findex_mm.entry_table.len() - ); - println!( - "Entry Table length: {}", - findex.findex_graph.findex_mm.entry_table.size() - ); - println!( - "Chain Table length: {}", - findex.findex_graph.findex_mm.chain_table.len() - ); - println!( - "Chain Table length: {}", - findex.findex_graph.findex_mm.chain_table.size() - ); - - for power in 0..=3 { - let n_keywords = 10usize.pow(power); - let keywords = prepare_keywords(n_keywords); - group.bench_function(format!("Searching {n_keywords} keyword(s)"), |b| { - b.iter(|| { - block_on(findex.search( - &key, - &label, - Keywords::from(keywords.clone()), - &|_| async { Ok(false) }, - )) - .expect("search failed"); - }); - }); - } - group.finish(); -} - -fn bench_upsert(c: &mut Criterion) { - // - // Generate new dataset - // - let mut group = c.benchmark_group("upsert"); - - let mut rng = CsRng::from_entropy(); - let label = Label::random(&mut rng); - let mut findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - let key = findex.keygen(); - - for power in 1..=3 { - let n_keywords = 10usize.pow(power); - let locations_and_words = prepare_locations_and_words(n_keywords); - group.bench_function(format!("Upserting {n_keywords} keyword(s)"), |b| { - b.iter(|| { - block_on(findex.add(&key, &label, locations_and_words.clone())) - .expect("upsert failed"); - findex.findex_graph.findex_mm.entry_table.0.flush(); - findex.findex_graph.findex_mm.chain_table.0.flush(); - }); - }); - } - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default().sample_size(5000); - targets = - bench_search, - bench_upsert, -); - -criterion_main!(benches); diff --git a/benches/generate.sh b/benches/generate.sh deleted file mode 100644 index d20e3e84..00000000 --- a/benches/generate.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -# Usage: bash generate.sh - -set -e - -cargo install cargo-criterion -cargo install criterion-table - -cargo criterion --features in_memory --message-format=json | criterion-table >benches/BENCHMARKS.md - -sed -i "s/❌ //g" benches/BENCHMARKS*.md -sed -i "s/🚀 //g" benches/BENCHMARKS*.md diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index d0928706..00000000 --- a/examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*flamegraph* diff --git a/examples/search.rs b/examples/search.rs deleted file mode 100644 index 6e2bd2d6..00000000 --- a/examples/search.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use cosmian_findex::{ - ChainTable, Data, DxEnc, EntryTable, Findex, InMemoryDb, Index, IndexedValue, - IndexedValueToKeywordsMap, Keyword, Keywords, Label, -}; -use futures::executor::block_on; - -fn prepare_keywords(number: i64) -> Keywords { - let mut keywords = HashSet::new(); - for idx in 0..number { - keywords.insert(Keyword::from(format!("name_{idx}").as_str())); - } - Keywords::from(keywords) -} - -fn prepare_locations_and_words(number: i64) -> IndexedValueToKeywordsMap { - let mut locations_and_words = HashMap::new(); - for idx in 0..number { - let mut words = HashSet::new(); - words.insert(Keyword::from(format!("first_name_{idx}").as_bytes())); - words.insert(Keyword::from(format!("name_{idx}").as_bytes())); - - locations_and_words.insert( - IndexedValue::Data(Data::from(idx.to_be_bytes().as_slice())), - Keywords::from(words.clone()), - ); - } - IndexedValueToKeywordsMap::from(locations_and_words) -} - -fn main() { - let locations_and_words = prepare_locations_and_words(10000); - - // - // Prepare indexes to be search - // - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("label"); - block_on(findex.add(&key, &label, locations_and_words)).expect("msg"); - - // - // Search 1000 words - // - let keywords = prepare_keywords(1000); - for _ in 0..1000 { - block_on(findex.search(&key, &label, keywords.clone(), &|_| async { Ok(false) })) - .expect("search failed"); - } -} diff --git a/examples/upsert.rs b/examples/upsert.rs deleted file mode 100644 index 0d578fa0..00000000 --- a/examples/upsert.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::collections::HashMap; - -use cosmian_findex::{ - ChainTable, Data, DxEnc, EntryTable, Findex, InMemoryDb, Index, IndexedValue, - IndexedValueToKeywordsMap, Keyword, Keywords, Label, -}; -use futures::executor::block_on; - -fn main() { - let mut indexed_value_to_keywords = HashMap::new(); - - // direct location robert doe - let robert_doe_location = Data::from("robert doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_doe_location), - Keywords::from_iter(["robert", "doe"]), - ); - - // direct location john doe - let john_doe_location = Data::from("john doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(john_doe_location), - Keywords::from_iter(["john", "doe"]), - ); - - // direct location for rob... - let rob_location = Data::from("rob DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(rob_location), - Keywords::from_iter(["rob"]), - ); - // ... and indirection to robert - indexed_value_to_keywords.insert( - IndexedValue::Pointer(Keyword::from("robert")), - Keywords::from_iter(["rob"]), - ); - - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("label"); - - for _ in 0..1_000_000 { - block_on(findex.add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords.clone()), - )) - .unwrap(); - } -} diff --git a/src/address.rs b/src/address.rs new file mode 100644 index 00000000..4c579da8 --- /dev/null +++ b/src/address.rs @@ -0,0 +1,28 @@ +use std::ops::{Deref, DerefMut}; + +use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Address([u8; LENGTH]); + +impl Deref for Address { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Address { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Address { + pub fn random(rng: &mut impl CryptoRngCore) -> Self { + let mut res = Self([0; LENGTH]); + rng.fill_bytes(&mut res); + res + } +} diff --git a/src/dem.rs b/src/dem.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/edx/chain_table.rs b/src/edx/chain_table.rs deleted file mode 100644 index 6d438393..00000000 --- a/src/edx/chain_table.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! Implements the Chain Table algorithm. -//! -//! This algorithm is in charge of storing the lists of values indexed by -//! Findex. Formally, it implements an Encrypted Dictionary (EDX) scheme. -//! -//! The encryption scheme used is AES256-GCM. - -use std::{ - collections::{HashMap, HashSet}, - ops::Deref, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::{kdf256, reexport::rand_core::CryptoRngCore, SymmetricKey}; - -use super::structs::Token; -use crate::{ - edx::{ - structs::{EdxKey, Seed}, - DbInterface, DxEnc, - }, - error::Error, - parameters::{SEED_LENGTH, TOKEN_LENGTH}, - EncryptedValue, Label, -}; - -/// Chain Table representation. -#[derive(Debug)] -pub struct ChainTable>(pub Edx); - -impl> Deref - for ChainTable -{ - type Target = Edx; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -const CHAIN_TABLE_KEY_DERIVATION_INFO: &[u8] = b"Chain Table key derivation info."; - -#[async_trait(?Send)] -impl> DxEnc - for ChainTable -{ - type EncryptedValue = EncryptedValue; - type Error = Error; - type Key = EdxKey; - type Seed = Seed; - type Database = Db; - - fn setup(edx: Self::Database) -> Self { - Self(edx) - } - - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed { - Seed::new(rng) - } - - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key { - let mut kmac_key = SymmetricKey::default(); - kdf256!( - &mut *kmac_key, - seed.as_ref(), - CHAIN_TABLE_KEY_DERIVATION_INFO, - b"KMAC key" - ); - let mut aead_key = SymmetricKey::default(); - kdf256!( - &mut *aead_key, - seed.as_ref(), - CHAIN_TABLE_KEY_DERIVATION_INFO, - b"DEM key" - ); - Self::Key { - token: kmac_key, - value: aead_key, - } - } - - fn tokenize(&self, key: &Self::Key, bytes: &[u8], _label: Option<&Label>) -> Token { - kmac!( - TOKEN_LENGTH, - &key.token, - bytes, - CHAIN_TABLE_KEY_DERIVATION_INFO - ) - .into() - } - - async fn get( - &self, - tokens: HashSet, - ) -> Result, Self::Error> { - self.0 - .fetch(tokens.into()) - .await - .map_err(Error::DbInterface) - .map(Into::into) - } - - fn resolve( - &self, - key: &Self::Key, - encrypted_value: &Self::EncryptedValue, - ) -> Result<[u8; VALUE_LENGTH], Self::Error> { - encrypted_value.decrypt(&key.value).map_err(Error::from) - } - - fn prepare( - &self, - rng: &mut impl CryptoRngCore, - key: &Self::Key, - value: [u8; VALUE_LENGTH], - ) -> Result { - Self::EncryptedValue::encrypt(rng, &key.value, value).map_err(Error::from) - } - - async fn upsert( - &self, - _old_values: HashMap, - _new_values: HashMap, - ) -> Result, Self::Error> { - panic!("The Chain Table does not do any upsert.") - } - - async fn insert(&self, items: HashMap) -> Result<(), Self::Error> { - self.0 - .insert(items.into()) - .await - .map_err(Error::DbInterface) - } - - async fn delete(&self, items: HashSet) -> Result<(), Self::Error> { - self.0 - .delete(items.into()) - .await - .map_err(Error::DbInterface) - } -} - -#[cfg(test)] -mod tests { - - use cosmian_crypto_core::{ - reexport::rand_core::{RngCore, SeedableRng}, - CsRng, - }; - - use super::*; - use crate::edx::in_memory::InMemoryDb; - - const VALUE_LENGTH: usize = 32; - - #[actix_rt::test] - async fn test_edx() { - let mut rng = CsRng::from_entropy(); - - let table = ChainTable::setup(InMemoryDb::default()); - let seed = table.gen_seed(&mut rng); - let key = table.derive_keys(&seed); - let label = Label::random(&mut rng); - - let tag = "only value"; - let token = table.tokenize(&key, tag.as_bytes(), Some(&label)); - - let mut value = [0; VALUE_LENGTH]; - rng.fill_bytes(&mut value); - - let encrypted_value = table.prepare(&mut rng, &key, value).unwrap(); - - table - .insert(HashMap::from_iter([(token, encrypted_value)])) - .await - .unwrap(); - - let res = table - .get(HashSet::from_iter([token])) - .await - .unwrap() - .into_iter() - .collect::>(); - - assert_eq!(res.len(), 1); - let ciphertext = res.get(&token).unwrap(); - let decrypted_value = table.resolve(&key, ciphertext).unwrap(); - assert_eq!(decrypted_value, value); - } -} diff --git a/src/edx/entry_table.rs b/src/edx/entry_table.rs deleted file mode 100644 index 7f41c281..00000000 --- a/src/edx/entry_table.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! The Entry Table is an Encrypted Dictionary scheme (EDX). It is used to -//! securely store chain metadata. -//! -//! It uses the AES256-GCM algorithm in order to encrypt its values and the -//! KMAC256 algorithm in order to derive secure tokens from tags. - -use std::{ - collections::{HashMap, HashSet}, - ops::Deref, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::{kdf256, reexport::rand_core::CryptoRngCore, SymmetricKey}; - -use super::{ - structs::{EdxKey, Seed, Token}, - TokenDump, -}; -use crate::{ - edx::{DbInterface, DxEnc}, - parameters::{SEED_LENGTH, TOKEN_LENGTH}, - EncryptedValue, Error, Label, -}; - -/// Implementation of the Entry Table EDX. -#[derive(Debug)] -pub struct EntryTable>(pub Edx); - -impl> Deref - for EntryTable -{ - type Target = Edx; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -const ENTRY_TABLE_KEY_DERIVATION_INFO: &[u8] = b"Entry Table key derivation info."; - -#[async_trait(?Send)] -impl> DxEnc - for EntryTable -{ - type EncryptedValue = EncryptedValue; - type Error = Error; - type Key = EdxKey; - type Seed = Seed; - type Database = Edx; - - fn setup(edx: Self::Database) -> Self { - Self(edx) - } - - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed { - Seed::new(rng) - } - - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key { - let mut kmac_key = SymmetricKey::default(); - kdf256!( - &mut *kmac_key, - seed.as_ref(), - ENTRY_TABLE_KEY_DERIVATION_INFO, - b"KMAC key" - ); - let mut aead_key = SymmetricKey::default(); - kdf256!( - &mut *aead_key, - seed.as_ref(), - ENTRY_TABLE_KEY_DERIVATION_INFO, - b"DEM key" - ); - Self::Key { - token: kmac_key, - value: aead_key, - } - } - - fn tokenize(&self, key: &Self::Key, bytes: &[u8], label: Option<&Label>) -> Token { - if let Some(label) = label { - kmac!(TOKEN_LENGTH, &key.token, bytes, label).into() - } else { - kmac!(TOKEN_LENGTH, &key.token, bytes, &[]).into() - } - } - - async fn get( - &self, - tokens: HashSet, - ) -> Result, Self::Error> { - self.0 - .fetch(tokens.into()) - .await - .map_err(Self::Error::from) - .map(Into::into) - } - - fn resolve( - &self, - key: &Self::Key, - encrypted_value: &Self::EncryptedValue, - ) -> Result<[u8; VALUE_LENGTH], Self::Error> { - encrypted_value.decrypt(&key.value).map_err(Error::from) - } - - async fn upsert( - &self, - old_values: HashMap, - new_values: HashMap, - ) -> Result, Self::Error> { - self.0 - .upsert(old_values.into(), new_values.into()) - .await - .map_err(Self::Error::from) - .map(Into::into) - } - - async fn insert(&self, items: HashMap) -> Result<(), Self::Error> { - self.0 - .insert(items.into()) - .await - .map_err(Error::DbInterface) - } - - fn prepare( - &self, - rng: &mut impl CryptoRngCore, - key: &Self::Key, - value: [u8; VALUE_LENGTH], - ) -> Result { - Self::EncryptedValue::encrypt(rng, &key.value, value).map_err(Error::from) - } - - async fn delete(&self, items: HashSet) -> Result<(), Self::Error> { - self.0 - .delete(items.into()) - .await - .map_err(Self::Error::DbInterface) - } -} - -#[async_trait(?Send)] -impl> TokenDump - for EntryTable -{ - type Error = >::Error; - - async fn dump_tokens(&self) -> Result, Self::Error> { - self.0 - .dump_tokens() - .await - .map_err(Error::DbInterface) - .map(Into::into) - } -} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - reexport::rand_core::{RngCore, SeedableRng}, - CsRng, - }; - - use super::*; - use crate::edx::in_memory::InMemoryDb; - - const VALUE_LENGTH: usize = 32; - - #[actix_rt::test] - async fn test_edx() { - let mut rng = CsRng::from_entropy(); - - let table = EntryTable::setup(InMemoryDb::default()); - let seed = table.gen_seed(&mut rng); - let key = table.derive_keys(&seed); - let label = Label::random(&mut rng); - - let tag = "only value"; - let token = table.tokenize(&key, tag.as_bytes(), Some(&label)); - - let mut value = [0; VALUE_LENGTH]; - rng.fill_bytes(&mut value); - - let encrypted_value = table.prepare(&mut rng, &key, value).unwrap(); - table - .upsert( - HashMap::new(), - HashMap::from_iter([(token, encrypted_value)]), - ) - .await - .unwrap(); - - let res = table - .get(HashSet::from_iter([token])) - .await - .unwrap() - .into_iter() - .collect::>(); - - assert_eq!(res.len(), 1); - let ciphertext = res.get(&token).unwrap(); - let decrypted_value = table.resolve(&key, ciphertext).unwrap(); - assert_eq!(decrypted_value, value); - } -} diff --git a/src/edx/mod.rs b/src/edx/mod.rs deleted file mode 100644 index 0f64cae4..00000000 --- a/src/edx/mod.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! A Dictionary Encryption Scheme securely stores constant length values inside -//! an Encrypted Dictionary (EDX). - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use zeroize::ZeroizeOnDrop; - -pub mod chain_table; -pub mod entry_table; -mod structs; - -pub use structs::{ - EncryptedValue, Token, TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens, -}; - -use crate::{DbInterfaceErrorTrait, Label}; - -#[async_trait(?Send)] -pub trait TokenDump { - type Error; - - async fn dump_tokens(&self) -> Result, Self::Error>; -} - -#[async_trait(?Send)] -pub trait DxEnc { - /// Seed used to derive the key. - type Seed: Sized + ZeroizeOnDrop + AsRef<[u8]> + Default + AsMut<[u8]>; - - /// Cryptographically secure key. - type Key: Sized + ZeroizeOnDrop; - - /// Type of error returned by the scheme. - type Error: std::error::Error; - - /// Fixed length encrypted value stored inside the encrypted dictionary. - type EncryptedValue: Debug + Sized + Clone; - - /// Backend storage. - type Database: DbInterface; - - /// Instantiates a new Dx-Enc scheme. - fn setup(edx: Self::Database) -> Self; - - /// Generates a new random seed. - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed; - - /// Deterministically derives a cryptographic key from the given seed. - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key; - - /// Deterministically transforms the given bytes into a cryptographically - /// secure token using the given key. - fn tokenize(&self, key: &Self::Key, bytes: &[u8], label: Option<&Label>) -> Token; - - /// Queries the given tokens and returns the encrypted values. - async fn get( - &self, - tokens: HashSet, - ) -> Result, Self::Error>; - - /// Decrypts the given encrypted value with the given key. - fn resolve( - &self, - key: &Self::Key, - encrypted_value: &Self::EncryptedValue, - ) -> Result<[u8; VALUE_LENGTH], Self::Error>; - - /// Encrypts the given values using the given key. - fn prepare( - &self, - rng: &mut impl CryptoRngCore, - key: &Self::Key, - values: [u8; VALUE_LENGTH], - ) -> Result; - - /// Conditionally upserts the given items into the EDX. - async fn upsert( - &self, - old_values: HashMap, - new_values: HashMap, - ) -> Result, Self::Error>; - - /// Inserts the given items into the EDX. - /// - /// # Error - /// - /// Returns an error without inserting any value if the EDX already contains - /// a value for a given tokens. - async fn insert(&self, values: HashMap) - -> Result<(), Self::Error>; - - /// Deletes the given items from the EDX. - async fn delete(&self, tokens: HashSet) -> Result<(), Self::Error>; -} - -#[async_trait(?Send)] -pub trait DbInterface { - /// Type of error returned by the EDX. - type Error: DbInterfaceErrorTrait; - - /// Queries the EDX for all tokens stored. - async fn dump_tokens(&self) -> Result; - - /// Queries an Edx for the given tokens. Only returns a value for the tokens - /// that are present in the store. - async fn fetch( - &self, - tokens: Tokens, - ) -> Result, Self::Error>; - - /// Upserts the given values into the database for the given tokens. - /// - /// For each new token: - /// 1. if there is no old value and no value stored, inserts the new value; - /// 2. if there is an old value but no value stored, returns an error; - /// 3. if the old value is equal to the value stored, updates the value stored - /// with the new value; - /// 4. else returns the value stored with its associated token. - /// - /// A summary of the different cases is presented in the following table: - /// - /// +--------------+----------+-----------+-----------+ - /// | stored \ old | None | Some("A") | Some("B") | - /// +--------------+----------+-----------+-----------+ - /// | None | upserted | *error* | *error* | - /// | Some("A") | rejected | upserted | rejected | - /// | Some("B") | rejected | rejected | upserted | - /// +--------------+----------+-----------+-----------+ - /// - /// All modifications of the EDX should be *atomic*. - async fn upsert( - &self, - old_values: TokenToEncryptedValueMap, - new_values: TokenToEncryptedValueMap, - ) -> Result, Self::Error>; - - /// Inserts the given values into the Edx for the given tokens. - /// - /// # Error - /// - /// If a value is already stored for one of these tokens, no new value - /// should be inserted and an error should be returned. - async fn insert( - &self, - values: TokenToEncryptedValueMap, - ) -> Result<(), Self::Error>; - - /// Deletes the lines associated to the given tokens from the EDX. - async fn delete(&self, tokens: Tokens) -> Result<(), Self::Error>; -} - -#[cfg(any(test, feature = "in_memory"))] -pub mod in_memory { - use std::{ - collections::HashMap, - fmt::{Debug, Display}, - ops::Deref, - sync::{Arc, Mutex}, - }; - - use async_trait::async_trait; - use cosmian_crypto_core::CryptoCoreError; - #[cfg(feature = "in_memory")] - use cosmian_crypto_core::{bytes_ser_de::Serializable, Nonce}; - - use super::{ - DbInterface, Token, TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens, - }; - #[cfg(feature = "in_memory")] - use crate::parameters::{MAC_LENGTH, NONCE_LENGTH}; - use crate::{error::DbInterfaceErrorTrait, EncryptedValue}; - - #[derive(Debug)] - pub struct InMemoryDbError(String); - - impl From for InMemoryDbError { - fn from(value: CryptoCoreError) -> Self { - Self(value.to_string()) - } - } - - impl Display for InMemoryDbError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "callback error") - } - } - - impl std::error::Error for InMemoryDbError {} - impl DbInterfaceErrorTrait for InMemoryDbError {} - - #[derive(Debug)] - pub struct InMemoryDb( - Arc>>, - ); - - impl Deref for InMemoryDb { - type Target = Arc>>; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl Default for InMemoryDb { - fn default() -> Self { - Self(Default::default()) - } - } - - impl InMemoryDb { - #[must_use] - pub fn is_empty(&self) -> bool { - self.lock().expect("could not lock mutex").is_empty() - } - - #[must_use] - pub fn len(&self) -> usize { - self.lock().expect("could not lock mutex").len() - } - - #[must_use] - pub fn size(&self) -> usize { - self.len() * (Token::LENGTH + EncryptedValue::::LENGTH) - } - - pub fn flush(&mut self) { - *self.lock().expect("could not lock mutex") = TokenToEncryptedValueMap::default(); - } - - pub fn load(&mut self, table: TokenToEncryptedValueMap) { - *self.lock().expect("could not lock mutex") = table; - } - } - - #[cfg(feature = "in_memory")] - impl Serializable for InMemoryDb { - type Error = InMemoryDbError; - - fn length(&self) -> usize { - (self.lock().expect("could not lock mutex").deref()).len() - * (Token::LENGTH + NONCE_LENGTH + MAC_LENGTH + VALUE_LENGTH) - } - - fn write( - &self, - ser: &mut cosmian_crypto_core::bytes_ser_de::Serializer, - ) -> Result { - let table = &*self.lock().expect("could not lock mutex"); - let mut n = ser.write_leb128_u64(table.len() as u64)?; - for (k, v) in table.iter() { - n += ser.write_array(k)?; - n += ser.write_array(&v.nonce.0)?; - n += ser.write_array(&v.ciphertext)?; - n += ser.write_array(&v.tag)?; - } - Ok(n) - } - - fn read( - de: &mut cosmian_crypto_core::bytes_ser_de::Deserializer, - ) -> Result { - let n = de.read_leb128_u64()? as usize; - let mut table = HashMap::with_capacity(n); - for _ in 0..n { - let k = de.read_array::<{ Token::LENGTH }>()?; - // previous version used to write the size of the value. - let _ = de.read_leb128_u64(); - let nonce = Nonce::from(de.read_array::()?); - let ciphertext = de.read_array::()?; - let tag = de.read_array::()?; - table.insert( - Token::from(k), - EncryptedValue { - ciphertext, - tag, - nonce, - }, - ); - } - - Ok(Self(Arc::new(Mutex::new(TokenToEncryptedValueMap::from( - table, - ))))) - } - } - - #[async_trait(?Send)] - impl DbInterface for InMemoryDb { - type Error = InMemoryDbError; - - async fn dump_tokens(&self) -> Result { - Ok(self - .lock() - .expect("could not lock table") - .keys() - .copied() - .collect()) - } - - async fn fetch( - &self, - tokens: Tokens, - ) -> Result, InMemoryDbError> { - Ok(TokenWithEncryptedValueList::from( - tokens - .into_iter() - .filter_map(|token| { - self.lock() - .expect("couldn't lock the table") - .get(&token) - .cloned() - .map(|v| (token, v)) - }) - .collect::>(), - )) - } - - async fn upsert( - &self, - old_values: TokenToEncryptedValueMap, - new_values: TokenToEncryptedValueMap, - ) -> Result, InMemoryDbError> { - let edx = &mut self.lock().expect("couldn't lock the table"); - // Ensures an value is present inside the EDX for each given old value. - if old_values.keys().any(|token| !edx.contains_key(token)) { - return Err(InMemoryDbError(format!( - "missing EDX tokens {:?}", - old_values - .keys() - .filter(|token| !edx.contains_key(*token)) - .collect::>() - ))); - } - - let mut res = HashMap::new(); - for (token, new_ciphertext) in new_values { - let old_ciphertext = old_values.get(&token); - let edx_ciphertext = edx.get(&token); - - if old_ciphertext == edx_ciphertext { - edx.insert(token, new_ciphertext.clone()); - } else { - res.insert( - token, - edx_ciphertext - .cloned() - .expect("above check ensures this cannot happen"), - ); - } - } - - Ok(TokenToEncryptedValueMap::from(res)) - } - - async fn insert( - &self, - items: TokenToEncryptedValueMap, - ) -> Result<(), Self::Error> { - let edx = &mut self.lock().expect("couldn't lock the table"); - - if items.keys().any(|token| edx.contains_key(token)) { - return Err(InMemoryDbError(format!( - "cannot insert value for used tokens ({:?})", - items - .keys() - .filter(|token| edx.contains_key(*token)) - .collect::>() - ))); - } - - edx.extend(items); - - Ok(()) - } - - async fn delete(&self, items: Tokens) -> Result<(), Self::Error> { - let edx = &mut self.lock().expect("could not lock mutex"); - for token in &*items { - edx.remove(token); - } - Ok(()) - } - } -} diff --git a/src/edx/structs.rs b/src/edx/structs.rs deleted file mode 100644 index 08732f6e..00000000 --- a/src/edx/structs.rs +++ /dev/null @@ -1,393 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fmt::Display, - ops::{Deref, DerefMut}, - vec::IntoIter, -}; - -use base64::engine::{general_purpose::STANDARD, Engine}; -use cosmian_crypto_core::{ - reexport::rand_core::CryptoRngCore, Aes256Gcm, DemInPlace, FixedSizeCBytes, Instantiable, - Nonce, RandomFixedSizeCBytes, SymmetricKey, -}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::{ - error::CoreError, - parameters::{MAC_LENGTH, NONCE_LENGTH, SYM_KEY_LENGTH}, - TOKEN_LENGTH, -}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Token([u8; TOKEN_LENGTH]); - -impl Token { - pub const LENGTH: usize = TOKEN_LENGTH; -} - -impl Deref for Token { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for [u8; TOKEN_LENGTH] { - fn from(value: Token) -> Self { - value.0 - } -} - -impl From<[u8; TOKEN_LENGTH]> for Token { - fn from(bytes: [u8; TOKEN_LENGTH]) -> Self { - Self(bytes) - } -} - -impl TryFrom<&[u8]> for Token { - type Error = CoreError; - - fn try_from(value: &[u8]) -> Result { - <[u8; TOKEN_LENGTH]>::try_from(value) - .map_err(|_| { - CoreError::Conversion(format!( - "cannot create token from {} bytes, {TOKEN_LENGTH} expected", - value.len(), - )) - }) - .map(Into::into) - } -} - -impl Display for Token { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", STANDARD.encode(&**self)) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Tokens(pub HashSet); - -impl Deref for Tokens { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for Tokens { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "[")?; - for token in &self.0 { - writeln!(f, " {token},")?; - } - write!(f, "]") - } -} - -impl FromIterator for Tokens { - fn from_iter>(iter: T) -> Self { - Self(HashSet::from_iter(iter)) - } -} - -impl IntoIterator for Tokens { - type IntoIter = <::Target as IntoIterator>::IntoIter; - type Item = Token; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl From> for Tokens { - fn from(value: HashSet) -> Self { - Self(value) - } -} - -impl From for HashSet { - fn from(value: Tokens) -> Self { - value.0 - } -} - -/// Seed used to derive a key. -#[derive(Debug)] -pub struct Seed([u8; LENGTH]); - -impl Seed { - pub fn new(rng: &mut impl CryptoRngCore) -> Self { - let mut seed = [0; LENGTH]; - rng.fill_bytes(&mut seed); - Self(seed) - } -} - -impl Default for Seed { - fn default() -> Self { - Self([0; LENGTH]) - } -} - -impl From<[u8; LENGTH]> for Seed { - fn from(value: [u8; LENGTH]) -> Self { - Self(value) - } -} - -impl AsRef<[u8]> for Seed { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl AsMut<[u8]> for Seed { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -impl Zeroize for Seed { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl Drop for Seed { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl ZeroizeOnDrop for Seed {} - -/// Key used by the Dictionary Encryption Scheme. -/// -/// It is composed of two sub-keys: -/// - the token sub-key is used to generate secure tokens from tags; -/// - the value sub-key is used to encrypt the values stored. -pub struct EdxKey { - pub token: SymmetricKey<{ SYM_KEY_LENGTH }>, - pub value: SymmetricKey<{ SYM_KEY_LENGTH }>, -} - -impl ZeroizeOnDrop for EdxKey {} - -/// Value stored inside the EDX. It is composed of: -/// - a AESGCM-256 ciphertext; -/// - a nonce; -/// - a MAC tag. -/// -/// TODO: the nonce used to encrypt the values should be derived from the token -/// to avoid storing yet another random value. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EncryptedValue { - pub ciphertext: [u8; VALUE_LENGTH], - pub tag: [u8; MAC_LENGTH], - pub nonce: Nonce, -} - -impl From<&EncryptedValue> for Vec { - fn from(value: &EncryptedValue) -> Self { - let mut res = Self::with_capacity(EncryptedValue::::LENGTH); - res.extend(&value.nonce.0); - res.extend(&value.ciphertext); - res.extend(&value.tag); - res - } -} - -impl TryFrom<&[u8]> for EncryptedValue { - type Error = CoreError; - - fn try_from(value: &[u8]) -> Result { - if value.len() != Self::LENGTH { - return Err(Self::Error::Conversion(format!( - "incorrect length for encrypted value: {} bytes give, {} bytes expected", - value.len(), - Self::LENGTH - ))); - } - - let nonce = Nonce::try_from_slice(&value[..NONCE_LENGTH])?; - let ciphertext = - <[u8; VALUE_LENGTH]>::try_from(&value[NONCE_LENGTH..NONCE_LENGTH + VALUE_LENGTH]) - .map_err(|e| Self::Error::Conversion(e.to_string()))?; - let tag = <[u8; MAC_LENGTH]>::try_from(&value[NONCE_LENGTH + VALUE_LENGTH..]) - .map_err(|e| Self::Error::Conversion(e.to_string()))?; - Ok(Self { - ciphertext, - tag, - nonce, - }) - } -} - -impl EncryptedValue { - pub const LENGTH: usize = MAC_LENGTH + NONCE_LENGTH + VALUE_LENGTH; - - /// Encrypts the value using the given key. - pub fn encrypt( - rng: &mut impl CryptoRngCore, - key: &SymmetricKey, - value: [u8; VALUE_LENGTH], - ) -> Result { - let mut res = Self { - ciphertext: value, - nonce: Nonce::from([0; NONCE_LENGTH]), - tag: [0; MAC_LENGTH], - }; - rng.fill_bytes(&mut res.nonce.0); - let aead = Aes256Gcm::new(key); - let tag = aead - .encrypt_in_place_detached(&res.nonce, &mut res.ciphertext, None) - .map_err(CoreError::CryptoCore)?; - res.tag.copy_from_slice(tag.as_slice()); - Ok(res) - } - - /// Decrypts the value using the given key. - pub fn decrypt( - &self, - key: &SymmetricKey, - ) -> Result<[u8; VALUE_LENGTH], CoreError> { - let mut res = self.ciphertext; - let aead = Aes256Gcm::new(key); - aead.decrypt_in_place_detached(&self.nonce, &mut res, &self.tag, None) - .map_err(CoreError::CryptoCore)?; - Ok(res) - } -} - -impl Display for EncryptedValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ ciphertext: '{}', tag: '{}', nonce: '{}' }}", - STANDARD.encode(self.ciphertext), - STANDARD.encode(self.tag), - STANDARD.encode(self.nonce.as_bytes()) - ) - } -} - -#[derive(Debug, Default)] -pub struct TokenWithEncryptedValueList( - pub Vec<(Token, EncryptedValue)>, -); - -impl Deref for TokenWithEncryptedValueList { - type Target = [(Token, EncryptedValue)]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for TokenWithEncryptedValueList { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Token with EncryptedValue list: [")?; - for (token, encrypted_value) in self.iter() { - writeln!(f, " ({token}, {encrypted_value})")?; - } - writeln!(f, "]") - } -} - -impl From)>> - for TokenWithEncryptedValueList -{ - fn from(value: Vec<(Token, EncryptedValue)>) -> Self { - Self(value) - } -} - -impl From> - for Vec<(Token, EncryptedValue)> -{ - fn from(value: TokenWithEncryptedValueList) -> Self { - value.0 - } -} - -impl FromIterator<(Token, EncryptedValue)> - for TokenWithEncryptedValueList -{ - fn from_iter)>>(iter: T) -> Self { - Self(Vec::from_iter(iter)) - } -} - -impl IntoIterator for TokenWithEncryptedValueList { - type IntoIter = IntoIter<(Token, EncryptedValue)>; - type Item = (Token, EncryptedValue); - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct TokenToEncryptedValueMap( - pub HashMap>, -); - -impl Deref for TokenToEncryptedValueMap { - type Target = HashMap>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for TokenToEncryptedValueMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for TokenToEncryptedValueMap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Token to EncryptedValue map: {{")?; - for (token, encrypted_value) in self.iter() { - writeln!(f, " '{token}': {encrypted_value}")?; - } - writeln!(f, "}}") - } -} - -impl From>> - for TokenToEncryptedValueMap -{ - fn from(value: HashMap>) -> Self { - Self(value) - } -} - -impl From> - for HashMap> -{ - fn from(value: TokenToEncryptedValueMap) -> Self { - value.0 - } -} - -impl FromIterator<(Token, EncryptedValue)> - for TokenToEncryptedValueMap -{ - fn from_iter)>>(iter: T) -> Self { - Self(HashMap::from_iter(iter)) - } -} - -impl IntoIterator for TokenToEncryptedValueMap { - type IntoIter = <::Target as IntoIterator>::IntoIter; - type Item = <::Target as IntoIterator>::Item; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} diff --git a/src/error.rs b/src/error.rs index fccad338..4626835a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,71 +1,27 @@ -//! Defines error type and conversions for Findex. - -use core::fmt::{Debug, Display}; +use std::fmt::Display; use cosmian_crypto_core::CryptoCoreError; -use never::Never; - -pub trait DbInterfaceErrorTrait: std::error::Error {} #[derive(Debug)] -pub enum Error { - Crypto(String), - CryptoCore(CryptoCoreError), - Conversion(String), - DbInterface(T), - Interrupt(String), - Filter(String), +pub enum Error { + Parsing(String), + Encryption(CryptoCoreError), + Memory(MemoryError), } -impl Display for Error { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Crypto(msg) | Self::Conversion(msg) => { - write!(f, "crypto error: {msg}") - } - Self::CryptoCore(err) => write!(f, "CryptoCore error: {err}"), - Self::DbInterface(msg) => write!(f, "database interface error: {msg}"), - Self::Interrupt(error) => write!(f, "user interrupt error: {error}"), - Self::Filter(error) => write!(f, "user data filter error: {error}"), + Self::Parsing(e) => write!(f, "{}", e), + _ => write!(f, "Error"), } } } -impl From for Error { - fn from(e: std::num::TryFromIntError) -> Self { - Self::Conversion(e.to_string()) - } -} - -impl From for Error { - fn from(e: CryptoCoreError) -> Self { - Self::CryptoCore(e) - } -} +impl std::error::Error for Error {} -impl From for Error { - fn from(value: T) -> Self { - Self::DbInterface(value) - } -} - -impl std::error::Error for Error {} - -/// Alias used to represent a Findex error that does not originate from a -/// callback. -pub type CoreError = Error; - -impl From for Error { - fn from(value: CoreError) -> Self { - match value { - CoreError::Crypto(err) => Self::Crypto(err), - CoreError::CryptoCore(err) => Self::CryptoCore(err), - CoreError::Conversion(err) => Self::Conversion(err), - CoreError::DbInterface(_) => { - panic!("this cannot happen because CoreError uses the `Never` type"); - } - CoreError::Interrupt(err) => Self::Interrupt(err), - CoreError::Filter(err) => Self::Filter(err), - } +impl From for Error { + fn from(e: MemoryError) -> Self { + Self::Memory(e) } } diff --git a/src/findex_graph/compact.rs b/src/findex_graph/compact.rs deleted file mode 100644 index d0a0b5de..00000000 --- a/src/findex_graph/compact.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - hash::Hash, - sync::{Arc, Mutex}, -}; - -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use tracing::debug; - -use super::{FindexGraph, GxEnc}; -use crate::{ - edx::{Token, TokenDump}, - findex_mm::{CompactingData, ENTRY_LENGTH, LINK_LENGTH}, - DbInterfaceErrorTrait, DxEnc, Error, IndexedValue, Label, -}; - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc> + TokenDump>, - ChainTable: DxEnc>, - > FindexGraph -{ - pub async fn list_indexed_encrypted_tags(&self) -> Result, Error> { - self.findex_mm.dump_entry_tokens().await - } - - pub async fn prepare_compact< - Tag: Debug + Hash + Eq + Clone + AsRef<[u8]> + From>, - Value: Hash + Eq + Clone + From>, - >( - &self, - key: &>::Key, - tokens: HashSet, - compact_target: &HashSet, - ) -> Result< - ( - HashMap>>, - CompactingData, - ), - Error, - > { - let (indexed_values, data) = self - .findex_mm - .prepare_compacting(key, tokens, compact_target) - .await?; - let indexed_values = indexed_values - .into_iter() - .map(|(token, value)| { - value - .into_iter() - .map(|v| IndexedValue::::try_from(v.as_slice())) - .collect::>() - .map(|set| (token, set)) - }) - .collect::>()?; - Ok((indexed_values, data)) - } - - pub async fn complete_compacting< - Tag: Debug + Hash + Eq + Clone + AsRef<[u8]> + From>, - Value: Hash + Eq + Clone + AsRef<[u8]> + From>, - >( - &self, - rng: Arc>, - key: &>::Key, - label: &Label, - indexed_values: HashMap>>, - continuation: CompactingData, - ) -> Result<(), Error> { - debug!( - "complete_compacting: entering: indexed_values number: {}", - indexed_values.len() - ); - let indexed_values = indexed_values - .into_iter() - .map(|(token, values)| (token, values.iter().map(Into::into).collect())) - .collect(); - self.findex_mm - .complete_compacting(rng, key, indexed_values, continuation, label) - .await - } -} diff --git a/src/findex_graph/graph.rs b/src/findex_graph/graph.rs deleted file mode 100644 index b6a7915b..00000000 --- a/src/findex_graph/graph.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Implement GX-Enc for `FindexGraph`. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - future::Future, - hash::Hash, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; - -use crate::{ - findex_graph::{FindexGraph, GxEnc, IndexedValue}, - findex_mm::{FindexMultiMap, MmEnc, Operation, ENTRY_LENGTH, LINK_LENGTH}, - parameters::SEED_LENGTH, - DbInterfaceErrorTrait, DxEnc, Error, Label, -}; - -#[async_trait(?Send)] -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, - > GxEnc for FindexGraph -{ - type Error = - as MmEnc>::Error; - type Key = - as MmEnc>::Key; - type Seed = - as MmEnc>::Seed; - - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed { - self.findex_mm.gen_seed(rng) - } - - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key { - self.findex_mm.derive_keys(seed) - } - - async fn get< - Tag: Debug + Hash + Eq + Clone + AsRef<[u8]> + From>, - Value: Hash + Eq + Clone + From>, - F: Future>, - Interrupt: Fn(HashMap>>) -> F, - >( - &self, - key: &Self::Key, - mut tags: HashSet, - label: &Label, - interrupt: &Interrupt, - ) -> Result>>, Self::Error> { - let mut graph = HashMap::with_capacity(tags.len()); - - while !tags.is_empty() { - let indexed_values = self.findex_mm.get(key, tags, label).await?; - - // This is needed to avoid the need to have a mutable reference to the `graph` - // in the following `for` loop. Since having such a reference prevents calling - // `contains_key()` (in Rust a mutable reference cannot coexist with other - // references). - let mut local_graph = HashMap::with_capacity(indexed_values.len()); - - tags = HashSet::with_capacity( - indexed_values - .values() - .map(std::collections::HashSet::len) - .sum(), - ); - for (tag, values) in indexed_values { - let entry = local_graph - .entry(tag) - .or_insert_with(|| HashSet::with_capacity(values.len())); - for value in values { - let value = IndexedValue::::try_from(value.as_slice())?; - if let IndexedValue::Pointer(child) = &value { - if !graph.contains_key(child) { - // Marks the pointers to new tags to be searched at the next iteration. - tags.insert(child.clone()); - } - } - entry.insert(value); - } - } - - let is_interrupted = interrupt(local_graph.clone()) - .await - .map_err(Self::Error::Interrupt)?; - - if is_interrupted { - tags = HashSet::new(); - } - - graph.extend(local_graph); - } - - Ok(graph) - } - - async fn insert, Value: AsRef<[u8]>>( - &self, - rng: Arc>, - key: &Self::Key, - items: HashMap)>>, - label: &Label, - ) -> Result, Error> { - let items = items - .into_iter() - .map(|(tag, modifications)| { - let modifications = modifications - .into_iter() - .map(|(op, value)| (op, (&value).into())) - .collect(); - (tag, modifications) - }) - .collect(); - - self.findex_mm.insert(rng, key, items, label).await - } -} - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, - > FindexGraph -{ - /// Walks through the given graph from the given entry. Returns the set of - /// values found during the walk. - /// - /// In order not to enter cycles, the same node is not visited twice. This - /// is ensured by maintaining a set of visited nodes. - #[allow(clippy::only_used_in_recursion)] - pub fn walk<'a, Tag: Hash + Eq + Clone, Item: Clone + Hash + Eq>( - &self, - graph: &'a HashMap>>, - entry: &'a Tag, - visited: &mut HashSet<&'a Tag>, - ) -> HashSet { - if visited.contains(&entry) { - // Results associated to this tag have already been recovered. - return HashSet::new(); - } - - visited.insert(entry); - - let indexed_values = match graph.get(entry) { - Some(values) => values, - None => return HashSet::new(), - }; - - let mut res = HashSet::with_capacity(indexed_values.len()); - - for value in indexed_values { - match value { - IndexedValue::Pointer(child) => res.extend(self.walk(graph, child, visited)), - IndexedValue::Data(data) => { - res.insert(data.clone()); - } - } - } - - res - } -} diff --git a/src/findex_graph/mod.rs b/src/findex_graph/mod.rs deleted file mode 100644 index 70b10a30..00000000 --- a/src/findex_graph/mod.rs +++ /dev/null @@ -1,296 +0,0 @@ -//! Findex Graph Encryption Scheme (GX-Enc). -//! -//! Uses the Findex MM-Enc scheme in order to securely store a graph. This graph stores -//! `IndexedValue`s for `Tag`s. An `IndexedValue` can be either a `Pointer` to another `Tag` or a -//! `Data` containing a `Value`. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - future::Future, - hash::Hash, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use zeroize::ZeroizeOnDrop; - -use crate::{ - findex_mm::{FindexMultiMap, Operation, ENTRY_LENGTH, LINK_LENGTH}, - DbInterfaceErrorTrait, DxEnc, Error, Label, -}; - -mod compact; -mod graph; -mod structs; - -pub use structs::IndexedValue; - -#[async_trait(?Send)] -pub trait GxEnc { - /// Seed used to derive the key. - type Seed: Sized + ZeroizeOnDrop + AsRef<[u8]> + Default + AsMut<[u8]>; - - /// Cryptographic key. - type Key: Sized + ZeroizeOnDrop; - - /// Error type returned by the GxEnc scheme. - type Error: std::error::Error; - - /// Generates a new random seed. - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed; - - /// Deterministically derives a key from the given seed. - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key; - - /// Queries the encrypted graph for the given tags and returns the - /// decrypted values. - async fn get< - Tag: Debug + Hash + Eq + Clone + AsRef<[u8]> + From>, - Value: Hash + Eq + Clone + From>, - F: Future>, - Interrupt: Fn(HashMap>>) -> F, - >( - &self, - key: &Self::Key, - tags: HashSet, - label: &Label, - interrupt: &Interrupt, - ) -> Result>>, Self::Error>; - - /// Encrypts and inserts the given items into the graph. Returns the set of - /// tags added to the index. - #[allow(clippy::type_complexity)] - async fn insert, Value: AsRef<[u8]>>( - &self, - rng: Arc>, - key: &Self::Key, - items: HashMap)>>, - label: &Label, - ) -> Result, Self::Error>; -} - -#[derive(Debug)] -pub struct FindexGraph< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, -> { - pub findex_mm: FindexMultiMap, -} - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, - > FindexGraph -{ - pub fn new(entry_table: EntryTable, chain_table: ChainTable) -> Self { - Self { - findex_mm: FindexMultiMap { - entry_table, - chain_table, - }, - } - } -} - -#[cfg(test)] -mod tests { - use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - hash::Hash, - sync::{Arc, Mutex}, - }; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; - - use crate::{ - edx::in_memory::InMemoryDb, - findex_graph::{FindexGraph, GxEnc, IndexedValue}, - findex_mm::Operation, - ChainTable, DxEnc, EntryTable, Label, - }; - - async fn user_interrupt< - Tag: Debug + Hash + Eq + Clone + AsRef<[u8]> + From>, - Value: Hash + Eq + Clone + From>, - >( - _res: HashMap>>, - ) -> Result { - Ok(false) - } - - #[actix_rt::test] - async fn test_insert_get() { - let rng = Arc::new(Mutex::new(CsRng::from_entropy())); - let label = Label::random(&mut *rng.lock().expect("")); - - let entry_table = EntryTable::setup(InMemoryDb::default()); - let chain_table = ChainTable::setup(InMemoryDb::default()); - let findex = FindexGraph::new(entry_table, chain_table); - - let findex_seed = findex.gen_seed(&mut *rng.lock().expect("could not lock mutex")); - let findex_key = findex.derive_keys(&findex_seed); - - // Build the following cyclic index: - // - // a -> b -> c -> d -> h - // ^ | - // | v - // i -> g <- f <- e - // - // The results should be: - // - // { - // a: {L_b, L_c, L_d, L_h, L_e, L_f, L_g}, - // i: {L_g, L_b, L_c, L_d, L_h, L_e, L_f} - // } - // - let tag_a = b"tag a".to_vec(); - let tag_b = b"tag b".to_vec(); - let tag_c = b"tag c".to_vec(); - let tag_d = b"tag d".to_vec(); - let tag_e = b"tag e".to_vec(); - let tag_f = b"tag f".to_vec(); - let tag_g = b"tag g".to_vec(); - let tag_h = b"tag h".to_vec(); - let tag_i = b"tag i".to_vec(); - - let loc_a = b"location a".to_vec(); - let loc_b = b"location b".to_vec(); - let loc_c = b"location c".to_vec(); - let loc_d = b"location d".to_vec(); - let loc_e = b"location e".to_vec(); - let loc_f = b"location f".to_vec(); - let loc_g = b"location g".to_vec(); - let loc_h = b"location h".to_vec(); - let loc_i = b"location i".to_vec(); - - let mut cyclic_graph = HashMap::new(); - - cyclic_graph.insert( - tag_a.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_a.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_b.clone())), - ], - ); - cyclic_graph.insert( - tag_b.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_b.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_c.clone())), - ], - ); - cyclic_graph.insert( - tag_c.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_c.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_d.clone())), - ], - ); - cyclic_graph.insert( - tag_d.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_d.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_h.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_e.clone())), - ], - ); - cyclic_graph.insert( - tag_e.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_e.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_f.clone())), - ], - ); - cyclic_graph.insert( - tag_f.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_f.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_g.clone())), - ], - ); - cyclic_graph.insert( - tag_g.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_g.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_a.clone())), - ], - ); - cyclic_graph.insert( - tag_h.clone(), - vec![(Operation::Addition, IndexedValue::Data(loc_h.clone()))], - ); - cyclic_graph.insert( - tag_i.clone(), - vec![ - (Operation::Addition, IndexedValue::Data(loc_i.clone())), - (Operation::Addition, IndexedValue::Pointer(tag_g.clone())), - ], - ); - - // Upsert the graph. - findex - .insert(rng, &findex_key, cyclic_graph, &label) - .await - .unwrap(); - - let res = findex - .get( - &findex_key, - HashSet::from_iter([tag_a.clone(), tag_i.clone()]), - &label, - &user_interrupt, - ) - .await - .unwrap(); - - assert!(res.contains_key(&tag_a)); - assert!(res.contains_key(&tag_b)); - assert!(res.contains_key(&tag_c)); - assert!(res.contains_key(&tag_d)); - assert!(res.contains_key(&tag_e)); - assert!(res.contains_key(&tag_f)); - assert!(res.contains_key(&tag_g)); - assert!(res.contains_key(&tag_h)); - assert!(res.contains_key(&tag_i)); - - let res_a: HashSet> = findex.walk(&res, &tag_a, &mut HashSet::new()); - - assert!(res_a.contains(&loc_a)); - assert!(res_a.contains(&loc_b)); - assert!(res_a.contains(&loc_c)); - assert!(res_a.contains(&loc_d)); - assert!(res_a.contains(&loc_e)); - assert!(res_a.contains(&loc_f)); - assert!(res_a.contains(&loc_g)); - assert!(res_a.contains(&loc_h)); - - let res_i = findex.walk(&res, &tag_i, &mut HashSet::new()); - - assert!(res_i.contains(&loc_i)); - assert!(res_i.contains(&loc_b)); - assert!(res_i.contains(&loc_c)); - assert!(res_i.contains(&loc_d)); - assert!(res_i.contains(&loc_e)); - assert!(res_i.contains(&loc_f)); - assert!(res_i.contains(&loc_g)); - assert!(res_i.contains(&loc_h)); - - println!( - "ET length ({} lines), size ({}B)", - findex.findex_mm.entry_table.0.len(), - findex.findex_mm.entry_table.0.size() - ); - - println!( - "CT length ({} lines), size ({}B)", - findex.findex_mm.chain_table.0.len(), - findex.findex_mm.chain_table.0.size() - ); - } -} diff --git a/src/findex_graph/structs.rs b/src/findex_graph/structs.rs deleted file mode 100644 index 00481d30..00000000 --- a/src/findex_graph/structs.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Structures used by `FindexGraph`. - -use std::fmt::Display; - -use crate::error::CoreError; - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum IndexedValue { - Pointer(Tag), - Data(Data), -} - -impl Display for IndexedValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pointer(keyword) => write!(f, "IndexedValue::Pointer({keyword})"), - Self::Data(data) => write!(f, "IndexedValue::Data({data})"), - } - } -} - -impl IndexedValue { - pub fn get_data(&self) -> Option<&Data> { - match self { - Self::Pointer(_) => None, - Self::Data(data) => Some(data), - } - } - - pub fn get_pointer(&self) -> Option<&Tag> { - match self { - Self::Pointer(pointer) => Some(pointer), - Self::Data(_) => None, - } - } - - pub fn is_pointer(&self) -> bool { - matches!(self, Self::Pointer(_)) - } -} - -impl, Data: AsRef<[u8]>> From<&IndexedValue> for Vec { - fn from(value: &IndexedValue) -> Self { - match value { - IndexedValue::Pointer(pointer) => { - let pointer = pointer.as_ref(); - let mut b = Self::with_capacity(pointer.len() + 1); - b.push(b'w'); - b.extend(pointer); - b - } - IndexedValue::Data(data) => { - let data = data.as_ref(); - let mut b = Self::with_capacity(data.len() + 1); - b.push(b'l'); - b.extend(data); - b - } - } - } -} - -impl>, Data: From>> TryFrom<&[u8]> for IndexedValue { - type Error = CoreError; - - fn try_from(value: &[u8]) -> Result { - if value.len() < 2 { - return Err(Self::Error::Conversion(format!( - "indexed values should be at least two bytes long, {} given", - value.len() - ))); - } - // TODO: change the leading value in v.7 - match value[0] { - b'w' => Ok(Self::Pointer(value[1..].to_vec().into())), - b'l' => Ok(Self::Data(value[1..].to_vec().into())), - _ => Err(Self::Error::Conversion(format!( - "indexed value should start by {} or {}, not `{}`", - b'w', b'l', &value[0] - ))), - } - } -} diff --git a/src/findex_mm/compact.rs b/src/findex_mm/compact.rs deleted file mode 100644 index 32d5d426..00000000 --- a/src/findex_mm/compact.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, -}; - -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use tracing::debug; - -use super::{structs::Entry, Operation}; -use crate::{ - edx::{Token, TokenDump}, - findex_mm::{structs::Link, CompactingData, FindexMultiMap, MmEnc}, - parameters::{BLOCK_LENGTH, LINE_WIDTH, SEED_LENGTH}, - DbInterfaceErrorTrait, DxEnc, Error, Label, ENTRY_LENGTH, LINK_LENGTH, -}; - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc> + TokenDump>, - ChainTable: DxEnc>, - > FindexMultiMap -{ - /// Returns the set of Entry Table tokens. - pub async fn dump_entry_tokens(&self) -> Result, Error> { - Ok(self.entry_table.dump_tokens().await?.into_iter().collect()) - } - - /// Fetches all chains associated to the given tokens. - /// - /// # Returns - /// - /// All the data needed to compact targeted chains only. - pub async fn prepare_compacting( - &self, - key: &>::Key, - tokens: HashSet, - compact_target: &HashSet, - ) -> Result<(HashMap>>, CompactingData), Error> - { - let entries = self.fetch_entries(key, tokens).await?; - - let n_entries = entries.len(); - let entries = entries.into_iter().try_fold( - HashMap::with_capacity(n_entries), - |mut acc, (k, v)| { - let old_v = acc.insert(k, v); - if old_v.is_some() { - Err(Error::::Crypto( - "Entry Table keys are not unique".to_string(), - )) - } else { - Ok(acc) - } - }, - )?; - - let chain_metadata = entries - .iter() - .filter(|(token, _)| compact_target.contains(token)) - .map(|(token, entry)| (*token, self.derive_metadata(entry))) - .collect::>(); - - let encrypted_links = self - .chain_table - .get( - chain_metadata - .iter() - .flat_map(|(_, (_, chain_tokens))| chain_tokens) - .copied() - .collect(), - ) - .await?; - - let n_links = encrypted_links.len(); - let encrypted_links = encrypted_links.into_iter().try_fold( - HashMap::with_capacity(n_links), - |mut acc, (k, v)| { - let old_v = acc.insert(k, v); - if old_v.is_some() { - Err(Error::::Crypto( - "Entry Table keys are not unique".to_string(), - )) - } else { - Ok(acc) - } - }, - )?; - - let indexed_chains = chain_metadata - .iter() - .map(|(entry_token, (chain_key, chain_tokens))| { - let links = chain_tokens - .iter() - .filter_map(|token| encrypted_links.get(token)) - .map(|encrypted_link| { - self.chain_table - .resolve(chain_key, encrypted_link) - .map(Link) - }) - // Collect in a vector to preserve the chain order. - .collect::, _>>()?; - - Ok(( - *entry_token, - self.recompose::(&links)?, - )) - }) - .collect::>>()?; - - Ok(( - indexed_chains, - CompactingData { - metadata: chain_metadata, - entries, - }, - )) - } - - /// Completes the compacting operation: - /// 1. computes new links from the given `indexed_values` and updates - /// associated entries. - /// 2. uses the `new_key` to generate a new token and encrypt each entry - /// 3. tries applying modifications, reverts modifications upon failure or - /// remove old data upon success - #[tracing::instrument(skip_all)] - pub async fn complete_compacting( - &self, - rng: Arc>, - new_key: &>::Key, - remaining_associations: HashMap>>, - mut continuation: CompactingData, - new_label: &Label, - ) -> Result<(), Error> { - let remaining_entry_tokens = continuation - .entries - .keys() - .filter(|token| { - remaining_associations - .get(token) - .map_or(true, |associated_values| !associated_values.is_empty()) - }) - .copied() - .collect::>(); - - debug!( - "Step 1: computes new chains from the given `indexed_map` and updates associated \ - entries." - ); - - // Allocates a lower bound on the number of links. - let mut new_links = HashMap::with_capacity(remaining_associations.len()); - for (entry_token, chain_values) in remaining_associations { - let chain_links = self.decompose::( - &chain_values - .into_iter() - .map(|v| (Operation::Addition, v)) - .collect::>(), - )?; - - let old_entry = continuation.entries.get_mut(&entry_token).ok_or_else(|| { - Error::::Crypto(format!( - "{entry_token:?} not found in entries from the continuation data" - )) - })?; - - let rng = &mut *rng.lock().expect("could not lock mutex"); - let mut new_entry = - Entry::new(self.chain_table.gen_seed(rng), old_entry.tag_hash, None); - - let chain_key = self.chain_table.derive_keys(&new_entry.seed); - let chain_tokens = - self.derive_chain_tokens(&chain_key, new_entry.tag_hash.into(), chain_links.len()); - new_entry.chain_token = chain_tokens.last().copied(); - for (token, link) in chain_tokens.into_iter().zip(chain_links) { - new_links.insert( - token, - self.chain_table.prepare(&mut *rng, &chain_key, link.0)?, - ); - } - *old_entry = new_entry; - } - - let old_links = continuation - .metadata - .values() - .flat_map(|(_, chain_tokens)| chain_tokens) - .copied() - .collect(); - - debug!("Step 2: uses the `new_key` to generate a new token and encrypt each entry"); - - let mut old_entries = HashSet::with_capacity(continuation.entries.len()); - let mut new_entries = HashMap::with_capacity(continuation.entries.len()); - { - let rng = &mut *rng.lock().expect("could not lock mutex"); - for (token, entry) in continuation.entries { - old_entries.insert(token); - if remaining_entry_tokens.get(&token).is_some() { - new_entries.insert( - self.entry_table - .tokenize(new_key, &entry.tag_hash, Some(new_label)), - self.entry_table.prepare(rng, new_key, entry.into())?, - ); - } - } - } - let new_links_tokens = new_links.keys().copied().collect(); - let new_entry_tokens = new_entries.keys().copied().collect(); - - debug!( - "Step 3: tries applying modifications, reverts modifications upon failure or removes \ - old data upon success" - ); - - let res = self.chain_table.insert(new_links).await; - if res.is_err() { - self.chain_table.delete(new_links_tokens).await?; - return Err(Error::Crypto(format!( - "An error occurred during the insert operation. All modifications were reverted. \ - ({res:?})" - ))); - }; - - let res = self.entry_table.insert(new_entries).await.map_err(|e| { - Error::Crypto(format!( - "An error occurred during the `insert` operation, all modifications were reverted: {e}" - )) - }); - - if res.is_ok() { - self.chain_table.delete(old_links).await?; - self.entry_table.delete(old_entries).await?; - } else { - self.chain_table.delete(new_links_tokens).await?; - self.entry_table.delete(new_entry_tokens).await?; - } - - res - } -} diff --git a/src/findex_mm/mm.rs b/src/findex_mm/mm.rs deleted file mode 100644 index 97c9540f..00000000 --- a/src/findex_mm/mm.rs +++ /dev/null @@ -1,507 +0,0 @@ -//! Implements MM-Enc for `FindexMultiMap`. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - hash::Hash, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use tiny_keccak::{Hasher, Sha3}; - -use crate::{ - edx::{DxEnc, Token}, - error::Error, - findex_mm::{ - structs::{Entry, Link, Operation}, - FindexMultiMap, MmEnc, ENTRY_LENGTH, LINK_LENGTH, - }, - parameters::{BLOCK_LENGTH, HASH_LENGTH, LINE_WIDTH, SEED_LENGTH}, - CoreError, DbInterfaceErrorTrait, Label, -}; - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, - > FindexMultiMap -{ - /// Instantiates a new `FindexMultiMap`. - pub fn new(entry_table: EntryTable, chain_table: ChainTable) -> Self { - Self { - entry_table, - chain_table, - } - } - - /// Derives all chain tokens from the given `seed` and `key`. - pub(crate) fn unroll( - &self, - key: &ChainTable::Key, - seed: &[u8; HASH_LENGTH], - last_token: &Token, - ) -> Vec { - let mut chain = Vec::new(); - chain.push(self.chain_table.tokenize(key, seed, None)); - while &chain[chain.len() - 1] != last_token { - chain.push( - self.chain_table - .tokenize(key, &chain[chain.len() - 1], None), - ); - } - chain - } - - /// Derives `n` new chain tokens following the `last_token`. - pub(crate) fn derive_chain_tokens( - &self, - ct_key: &ChainTable::Key, - mut last_token: Token, - n: usize, - ) -> Vec { - let mut res = Vec::with_capacity(n); - for _ in 0..n { - let new_token = self.chain_table.tokenize(ct_key, &last_token, None); - res.push(new_token); - last_token = new_token; - } - res - } - - /// Fetches the entries associated to the given tags. - async fn fetch_entries_by_tag>( - &self, - key: &EntryTable::Key, - tags: HashSet, - label: &Label, - ) -> Result)>, Error> { - let tokens = tags - .into_iter() - .map(|tag| { - let mut tag_hash = [0; HASH_LENGTH]; - let mut hasher = Sha3::v256(); - hasher.update(tag.as_ref()); - hasher.finalize(&mut tag_hash); - (self.entry_table.tokenize(key, &tag_hash, Some(label)), tag) - }) - .collect::>(); - - let entries = self - .fetch_entries(key, tokens.keys().copied().collect()) - .await?; - - Ok(entries - .into_iter() - .filter_map(|(token, entry)| tokens.get(&token).cloned().map(|tag| (tag, entry))) - .collect()) - } - - /// Fetches the Entry Table for the given tokens and decrypts the entries - /// using the given key. - pub(crate) async fn fetch_entries( - &self, - key: &EntryTable::Key, - tokens: HashSet, - ) -> Result)>, Error> { - self.entry_table - .get(tokens) - .await? - .into_iter() - .map(|(token, encrypted_entry)| { - self.entry_table - .resolve(key, &encrypted_entry) - .map(|entry| (token, Entry::from(entry))) - }) - .collect() - } - - /// Decomposes the given Findex index modifications into a sequence of Chain - /// Table values. - /// - /// # Description - /// - /// Pads each value into blocks and push these blocks into a chain link, - /// setting the flag bytes of each block according to the associated - /// operation. - pub(crate) fn decompose( - &self, - modifications: &[(Operation, >::Item)], - ) -> Result, CoreError> { - // Allocate a lower bound on the number of chain links. - let mut chain = Vec::with_capacity(modifications.len()); - let mut link = Link::new(); - let mut pos = 0; - - for (operation, value) in modifications { - let full_block_number = value.len() / BLOCK_LENGTH; - - for i in 0..full_block_number { - link.set_operation(pos, *operation)?; - link.set_block(pos, &value[i * BLOCK_LENGTH..(i + 1) * BLOCK_LENGTH], false)?; - pos += 1; - if pos == LINE_LENGTH { - chain.push(link); - link = Link::new(); - pos = 0; - } - } - - link.set_operation(pos, *operation)?; - link.set_block(pos, &value[full_block_number * BLOCK_LENGTH..], true)?; - pos += 1; - if pos == LINE_LENGTH { - chain.push(link); - link = Link::new(); - pos = 0; - } - } - - // Don't forget the last line if some blocks were written to it. - if pos != 0 { - chain.push(link); - } - - Ok(chain) - } - - /// Recomposes the given sequence of Chain Table values into Findex values. - /// No duplicated and no deleted value is returned. - /// - /// # Description - /// - /// Iterates over the blocks: - /// - stacks the blocks until reading a terminating block; - /// - merges the data from the stacked block and fill the stack; - /// - if this value was an addition, adds it to the set, otherwise removes - /// any matching value from the set. - // TODO (TBZ): take an iterator as input to avoid needless collections. - pub(crate) fn recompose( - &self, - chain: &[Link], - ) -> Result>::Item>, CoreError> { - // Allocate an upper bound on the number of values. - let mut indexed_values = HashSet::with_capacity(chain.len() * LINE_LENGTH); - let mut stack = Vec::new(); - let mut current_operation = None; - - for ct_value in chain { - for pos in 0..LINE_LENGTH { - let (is_terminating, data) = ct_value.get_block(pos)?; - let operation = ct_value.get_operation(pos)?; - - if current_operation.is_some() && current_operation.as_ref() != Some(&operation) { - return Err(CoreError::Crypto( - "findex value cannot be decomposed into blocks with different operations" - .to_string(), - )); - } - - if is_terminating { - let mut findex_value = - Vec::with_capacity(stack.len() * BLOCK_LENGTH + data.len()); - for block_data in stack { - findex_value.extend(block_data); - } - findex_value.extend(data); - - if Operation::Addition == operation { - indexed_values.insert(findex_value); - } else { - indexed_values.remove(&findex_value); - } - - current_operation = None; - stack = Vec::new(); - } else { - stack.push(data); - if current_operation.is_none() { - current_operation = Some(operation); - } - } - } - } - Ok(indexed_values) - } - - /// Derives the chain metadata from the given entry: - /// - the chain key - /// - the chain tokens - pub(crate) fn derive_metadata( - &self, - entry: &Entry, - ) -> (ChainTable::Key, Vec) { - let chain_key = self.chain_table.derive_keys(&entry.seed); - let chain_tokens = entry - .chain_token - .as_ref() - .map(|last_token| self.unroll(&chain_key, &entry.tag_hash, last_token)) - .unwrap_or_default(); - (chain_key, chain_tokens) - } - - /// Commits the given chain modifications into the Entry Table. - /// - /// Returns the chains to insert in the Chain Table. - async fn commit>( - &self, - rng: Arc>, - key: &EntryTable::Key, - label: &Label, - chain_additions: &HashMap>, - ) -> Result<(HashSet, HashMap)>), Error> { - // Compute the token associated to the modifications. - let mut chain_additions = chain_additions - .iter() - .map(|(tag, links)| { - let mut tag_hash = [0; HASH_LENGTH]; - let mut hasher = Sha3::v256(); - hasher.update(tag.as_ref()); - hasher.finalize(&mut tag_hash); - ( - tag, - ( - self.entry_table.tokenize(key, &tag_hash, Some(label)), - tag_hash, - links.len(), - ), - ) - }) - .collect::>(); - - let encrypted_entries = self - .entry_table - .get( - chain_additions - .values() - .map(|(token, _, _)| token) - .copied() - .collect(), - ) - .await?; - - // Assert only one old entry is found per token. - let mut encrypted_entries = encrypted_entries.into_iter().try_fold( - HashMap::with_capacity(chain_additions.len()), - |mut acc, (k, v)| { - let old_value = acc.insert(k, v); - if old_value.is_some() { - Err(CoreError::Crypto( - "multiple Entry Table values are not allowed in upsert mode".to_string(), - )) - } else { - Ok(acc) - } - }, - )?; - - let mut new_tags = HashSet::with_capacity(chain_additions.len()); - let mut chain = HashMap::with_capacity(chain_additions.len()); - - while !chain_additions.is_empty() { - let mut new_entries = HashMap::with_capacity(chain_additions.len()); - // Compute new chain tokens to insert modifications and update the associated - // entry. Create one if the associated tag was not indexed yet. - for (tag, (token, tag_hash, n_additions)) in &chain_additions { - let mut entry = if let Some(ciphertext) = encrypted_entries.get(token) { - Entry::::from(self.entry_table.resolve(key, ciphertext)?) - } else { - // This tag is not indexed yet in the Entry table. - new_tags.insert((*tag).clone()); - Entry { - seed: self - .chain_table - .gen_seed(&mut *rng.lock().expect("could not lock mutex")), - tag_hash: *tag_hash, - chain_token: None, - } - }; - - // TODO: a cache could be added to prevent computing the key at each loop - // iteration. - let chain_key = self.chain_table.derive_keys(&entry.seed); - - let chain_tokens = self.derive_chain_tokens( - &chain_key, - entry.chain_token.unwrap_or_else(|| entry.tag_hash.into()), - *n_additions, - ); - entry.chain_token = chain_tokens.last().copied(); - - chain.insert((*tag).clone(), (chain_key, chain_tokens)); - new_entries.insert( - *token, - self.entry_table.prepare( - &mut *rng.lock().expect("could not lock mutex"), - key, - entry.into(), - )?, - ); - } - - // 2 - Upsert new entries to the Entry Table. - encrypted_entries = self - .entry_table - .upsert(encrypted_entries, new_entries) - .await?; - chain_additions.retain(|_, (k, _, _)| encrypted_entries.contains_key(k)); - new_tags.retain(|tag| !chain_additions.contains_key(tag)); - } - - Ok((new_tags, chain)) - } -} - -#[async_trait(?Send)] -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, - > MmEnc for FindexMultiMap -{ - type Error = Error; - type Item = Vec; - type Key = EntryTable::Key; - type Seed = EntryTable::Seed; - - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed { - self.entry_table.gen_seed(rng) - } - - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key { - self.entry_table.derive_keys(seed) - } - - async fn get>( - &self, - key: &Self::Key, - tags: HashSet, - label: &Label, - ) -> Result>, Self::Error> { - let entries = self.fetch_entries_by_tag(key, tags, label).await?; - - let chain_metadata = entries - .into_iter() - .map(|(tag, entry)| (tag, self.derive_metadata(&entry))) - .collect::>(); - - let links = self - .chain_table - .get( - chain_metadata - .iter() - .flat_map(|(_, (_, tokens))| tokens) - .copied() - .collect(), - ) - .await? - .into_iter() - .collect::>(); - - let mut indexed_values = - HashMap::>::with_capacity(chain_metadata.len()); - - for (tag, (chain_key, chain_tokens)) in chain_metadata { - let chain_links = chain_tokens - .iter() - .filter_map(|token| links.get(token)) - .map(|ciphertext| self.chain_table.resolve(&chain_key, ciphertext).map(Link)) - .collect::, _>>()?; - - indexed_values - .entry(tag) - .or_default() - .extend(self.recompose::(&chain_links)?); - } - Ok(indexed_values) - } - - async fn insert>( - &self, - rng: Arc>, - key: &Self::Key, - modifications: HashMap>, - label: &Label, - ) -> Result, Self::Error> { - let chain_additions = modifications - .into_iter() - .map(|(tag, new_values)| { - self.decompose::(&new_values) - .map(|links| (tag, links)) - }) - .collect::>, _>>()?; - - let (new_tags, mut chain_tokens) = self - .commit(rng.clone(), key, label, &chain_additions) - .await?; - - let mut encrypted_links = HashMap::with_capacity( - chain_tokens - .values() - .map(|(_, chain_tokens)| chain_tokens.len()) - .sum(), - ); - - for (tag, links) in chain_additions { - let (chain_key, tokens) = chain_tokens.remove(&tag).ok_or_else(|| { - CoreError::Crypto("no token not found for tag {tag:?}".to_string()) - })?; - for (token, link) in tokens.into_iter().zip(links.into_iter()) { - encrypted_links.insert( - token, - self.chain_table.prepare( - &mut *rng.lock().expect("could not lock mutex"), - &chain_key, - link.0, - )?, - ); - } - } - - self.chain_table.insert(encrypted_links).await?; - - Ok(new_tags) - } -} - -#[cfg(test)] -mod tests { - use cosmian_crypto_core::{ - reexport::rand_core::{RngCore, SeedableRng}, - CsRng, - }; - - use super::*; - use crate::edx::{chain_table::ChainTable, entry_table::EntryTable, in_memory::InMemoryDb}; - - #[actix_rt::test] - async fn test_decompose_recompose() { - let mut rng = CsRng::from_entropy(); - - let entry_table = EntryTable::setup(InMemoryDb::default()); - let chain_table = ChainTable::setup(InMemoryDb::default()); - let findex = FindexMultiMap::new(entry_table, chain_table); - - let n = 10; - let mut values = HashSet::with_capacity(n); - for _ in 0..n { - let mut v = vec![0; 32]; - rng.fill_bytes(&mut v); - values.insert(v); - } - - let lines = findex - .decompose::( - &values - .iter() - .map(|v| (Operation::Addition, v.clone())) - .collect::>(), - ) - .unwrap(); - let res = findex - .recompose::(&lines) - .unwrap(); - assert_eq!(values, res); - } -} diff --git a/src/findex_mm/mod.rs b/src/findex_mm/mod.rs deleted file mode 100644 index 90e4d4f1..00000000 --- a/src/findex_mm/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Findex Multi-Map Encryption scheme (MM-Enc). -//! -//! The Findex MM encryption scheme is in charge of storing variable-length -//! chains of variable-length values into a dictionary storing constant length -//! values. -//! -//! Tho this aim, it uses an auxiliary EDX called Entry Table in order to store -//! chain metadata: -//! - a seed used to derive the chain keys -//! - a hash used to derive the EDX token for this value -//! - the last token of the chain -//! -//! The EDX used to store the chains is called Chain Table. Each chain component -//! is called a link. A chain is therefore a sequence of links. The key used to -//! encrypt a given chain is derived from the seed stored in its associated -//! entry. The Chain Table tokens are derived from each other using dedicated -//! key derived from the same entry seed. The first token is derived from the -//! hash stored in the metadata. -//! -//! The last chain token stored in the Entry Table along with conditional (cf -//! [`DxEnc::upsert()`](crate::DxEnc::upsert)), atomic modifications of this -//! table allow avoiding races between concurrent additions to the same chain. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - hash::Hash, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; -use zeroize::ZeroizeOnDrop; - -use crate::{edx::DxEnc, DbInterfaceErrorTrait, Error, Label}; - -mod compact; -mod mm; -mod structs; - -pub use structs::{CompactingData, Operation, ENTRY_LENGTH, LINK_LENGTH}; - -#[async_trait(?Send)] -pub trait MmEnc { - /// Seed used to derive the key. - type Seed: Sized + ZeroizeOnDrop + AsRef<[u8]> + Default + AsMut<[u8]>; - - /// Cryptographic key. - type Key: Sized + ZeroizeOnDrop; - - /// Type of the values stored inside the multi-map. - type Item; - - /// Error returned by the multi-map encryption scheme. - type Error: std::error::Error; - - /// Generates a new random seed. - fn gen_seed(&self, rng: &mut impl CryptoRngCore) -> Self::Seed; - - /// Deterministically derives a key from the given seed. - fn derive_keys(&self, seed: &Self::Seed) -> Self::Key; - - /// Queries the encrypted multi-map for the given tags and returns the - /// decrypted values. - async fn get>( - &self, - key: &Self::Key, - tags: HashSet, - label: &Label, - ) -> Result>, Self::Error>; - - /// Applies the given modifications to the encrypted multi-map. Returns the - /// set of Tags added to the Multi-Map. - async fn insert>( - &self, - rng: Arc>, - key: &Self::Key, - modifications: HashMap>, - label: &Label, - ) -> Result, Self::Error>; -} - -#[derive(Debug)] -pub struct FindexMultiMap< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, -> { - pub entry_table: EntryTable, - pub chain_table: ChainTable, -} - -#[cfg(test)] -mod tests { - use std::{ - collections::HashMap, - sync::{Arc, Mutex}, - }; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; - - use crate::{ - edx::{chain_table::ChainTable, entry_table::EntryTable, in_memory::InMemoryDb}, - findex_mm::{FindexMultiMap, MmEnc, Operation}, - DxEnc, Label, - }; - - #[actix_rt::test] - async fn test_insert_get() { - let rng = Arc::new(Mutex::new(CsRng::from_entropy())); - let label = Label::random(&mut *rng.lock().expect("")); - - let entry_table = EntryTable::setup(InMemoryDb::default()); - let chain_table = ChainTable::setup(InMemoryDb::default()); - let findex = FindexMultiMap::new(entry_table, chain_table); - - // Generates 10 chains of 32 bytes values. - let n = 10; - let mut chains = HashMap::with_capacity(n); - for i in 0..n { - let tag = format!("Tag {i}").as_bytes().to_vec(); - let values = (0..n) - .map(|j| { - ( - Operation::Addition, - format!("Value ({i},{j})").as_bytes().to_vec(), - ) - }) - .collect(); - chains.insert(tag, values); - } - - let findex_seed = findex.gen_seed(&mut *rng.lock().expect("could not lock mutex")); - let findex_key = findex.derive_keys(&findex_seed); - findex - .insert(rng, &findex_key, chains.clone(), &label) - .await - .unwrap(); - - let res = findex - .get(&findex_key, chains.keys().cloned().collect(), &label) - .await - .unwrap(); - - for (tag, chain) in chains { - let res = res.get(&tag).unwrap(); - - assert_eq!(chain.len(), res.len()); - - for (op, value) in chain { - if Operation::Addition == op { - assert!(res.contains(&value)); - } - } - } - } -} diff --git a/src/findex_mm/structs.rs b/src/findex_mm/structs.rs deleted file mode 100644 index 4ed0a82b..00000000 --- a/src/findex_mm/structs.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Structures used by `FindexMultiMap`. - -use std::{ - collections::HashMap, - fmt::{Debug, Display}, - hash::Hash, - ops::{Deref, DerefMut}, -}; - -use base64::engine::{general_purpose::STANDARD, Engine}; - -use crate::{ - edx::{DxEnc, Token}, - error::CoreError, - parameters::{BLOCK_LENGTH, HASH_LENGTH, LINE_WIDTH, SEED_LENGTH, TOKEN_LENGTH}, -}; - -/// Operation allowed to be performed on a multi-map. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Operation { - Addition, - Deletion, -} - -/// Value stored in the Entry Table by Findex. -/// -/// It is composed of a: -/// - Chain Table seed; -/// - Entry Table tag; -/// - counter (u32). -pub const ENTRY_LENGTH: usize = SEED_LENGTH + HASH_LENGTH + TOKEN_LENGTH; - -#[derive(Debug, Clone)] -pub struct Entry> { - pub seed: ChainTable::Seed, - pub tag_hash: [u8; HASH_LENGTH], - pub chain_token: Option, -} - -impl> Entry { - pub fn new( - seed: ChainTable::Seed, - tag_hash: [u8; HASH_LENGTH], - chain_token: Option, - ) -> Self { - Self { - seed, - tag_hash, - chain_token, - } - } -} - -impl> From> for [u8; ENTRY_LENGTH] { - fn from(value: Entry) -> Self { - let mut res = [0; ENTRY_LENGTH]; - res[..TOKEN_LENGTH].copy_from_slice( - value - .chain_token - .unwrap_or_else(|| Token::from([0; TOKEN_LENGTH])) - .as_ref(), - ); - res[TOKEN_LENGTH..TOKEN_LENGTH + SEED_LENGTH].copy_from_slice(value.seed.as_ref()); - res[TOKEN_LENGTH + SEED_LENGTH..].copy_from_slice(&value.tag_hash); - res - } -} - -impl> From<[u8; ENTRY_LENGTH]> for Entry -where - [(); 1 + (BLOCK_LENGTH + 1) * LINE_WIDTH]:, -{ - fn from(value: [u8; ENTRY_LENGTH]) -> Self { - let mut chain_token = [0; TOKEN_LENGTH]; - chain_token.copy_from_slice(&value[..TOKEN_LENGTH]); - let mut seed = ChainTable::Seed::default(); - seed.as_mut() - .copy_from_slice(&value[TOKEN_LENGTH..TOKEN_LENGTH + SEED_LENGTH]); - let mut tag_hash = [0; HASH_LENGTH]; - tag_hash.copy_from_slice(&value[TOKEN_LENGTH + SEED_LENGTH..]); - Self { - seed, - tag_hash, - chain_token: if [0; TOKEN_LENGTH] == chain_token { - None - } else { - Some(Token::from(chain_token)) - }, - } - } -} - -impl> Display for Entry -where - [(); 1 + (BLOCK_LENGTH + 1) * LINE_WIDTH]:, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{{ seed: '{}', tag: '{}', token: '{}' }}", - STANDARD.encode(&self.seed), - STANDARD.encode(self.tag_hash), - self.chain_token - .as_ref() - .map(std::string::ToString::to_string) - .unwrap_or_default() - ) - } -} - -pub const LINK_LENGTH: usize = 1 + (BLOCK_LENGTH + 1) * LINE_WIDTH; - -/// Value stored in the Chain Table by Findex. -/// -/// It is composed of a list of: -/// - one operation byte; -/// - a list of `LINE_LENGTH` blocks of: -/// - one length byte; -/// - `BLOCK_LENGTH` data bytes. -/// -/// The operation byte is used to write all the type bits operation bits into a -/// single byte rather than adding an entire byte per block. -/// -/// The length byte is used to store the length of the data written into the -/// block. The value `255` is used to mark the block as *non-terminating*. A -/// non-terminating block can only be full. -#[derive(Debug)] -pub struct Link(pub [u8; LINK_LENGTH]); - -impl Link { - /// Creates an empty Chain Table value. - pub fn new() -> Self { - Self([0; LINK_LENGTH]) - } - - /// Returns: - /// - `true` if the `pos`th block is a terminating block; - /// - the data stored in this block. - pub fn get_block(&self, pos: usize) -> Result<(bool, &[u8]), CoreError> { - if pos > LINE_WIDTH { - return Err(CoreError::Crypto(format!( - "cannot query a block at pos ({pos}) greater than the line length ({LINE_WIDTH})" - ))); - } - let block = &self.0[(1 + pos * (BLOCK_LENGTH + 1))..=((pos + 1) * (BLOCK_LENGTH + 1))]; - if block[0] == 255 { - Ok((false, &block[1..])) - } else { - Ok((true, &block[1..=usize::from(block[0])])) - } - } - - /// Writes the given data into the `pos`th block. Mark it as terminating if - /// `is_terminating` is set to `true`. - pub fn set_block( - &mut self, - pos: usize, - data: &[u8], - is_terminating: bool, - ) -> Result<(), CoreError> { - if pos > LINE_WIDTH { - return Err(CoreError::Crypto(format!( - "cannot modify a block at pos ({pos}) greater than the line length ({LINE_WIDTH})" - ))); - } - let block = &mut self.0[(1 + pos * (BLOCK_LENGTH + 1))..=((pos + 1) * (BLOCK_LENGTH + 1))]; - if is_terminating { - block[0] = u8::try_from(data.len())?; - } else { - block[0] = 255; - } - block[1..=data.len()].copy_from_slice(data); - Ok(()) - } - - /// Returns the operation associated to the `pos`th block. - pub fn get_operation(&self, pos: usize) -> Result { - if pos > LINE_WIDTH { - return Err(CoreError::Crypto(format!( - "cannot query a block at pos ({pos}) greater than the line length ({LINE_WIDTH})" - ))); - } - if (self.0[0] >> pos) & 1 == 1 { - Ok(Operation::Addition) - } else { - Ok(Operation::Deletion) - } - } - - /// Sets the operation associated to the `pos`th block. - pub fn set_operation(&mut self, pos: usize, op: Operation) -> Result<(), CoreError> { - if pos > LINE_WIDTH { - return Err(CoreError::Crypto(format!( - "cannot modify a block at pos ({pos}) greater than the line length ({LINE_WIDTH})" - ))); - } - if Operation::Addition == op { - self.0[0] |= 1 << pos; - } - Ok(()) - } -} - -impl Deref for Link { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Link { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -pub struct CompactingData> { - #[allow(clippy::type_complexity)] - pub(crate) metadata: HashMap)>, - pub(crate) entries: HashMap>, -} diff --git a/src/index/mod.rs b/src/index/mod.rs deleted file mode 100644 index 442cc95f..00000000 --- a/src/index/mod.rs +++ /dev/null @@ -1,437 +0,0 @@ -//! Easy to use `Index` interface for `Findex` that hides most cryptographic -//! details. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, - future::Future, - sync::{Arc, Mutex}, -}; - -use async_trait::async_trait; -use tracing::{instrument, trace}; - -use crate::{ - edx::{Token, TokenDump, Tokens}, - findex_graph::{FindexGraph, GxEnc}, - findex_mm::{Operation, ENTRY_LENGTH, LINK_LENGTH}, - DbInterfaceErrorTrait, DxEnc, Error, IndexedValue, -}; - -mod structs; - -use cosmian_crypto_core::{ - reexport::rand_core::{RngCore, SeedableRng}, - CsRng, RandomFixedSizeCBytes, -}; -pub use structs::{ - Data, IndexedValueToKeywordsMap, Keyword, KeywordToDataMap, Keywords, Label, UserKey, -}; - -/// User-friendly interface to the Findex algorithm. -#[async_trait(?Send)] -pub trait Index, ChainTable: DxEnc> { - /// Index error type. - type Error: std::error::Error; - - /// Instantiates a new index. - fn new(et: EntryTable, ct: ChainTable) -> Self; - - /// Generates a new random cryptographic key. - fn keygen(&self) -> UserKey; - - /// Searches the index for the given keywords. - /// - /// The `interrupt` callback is fed with the results of each graph search - /// iteration. Iterations are stopped if the `interrupt` returns `true`. - async fn search< - F: Future>, - Interrupt: Fn(HashMap>>) -> F, - >( - &self, - key: &UserKey, - label: &Label, - keywords: Keywords, - interrupt: &Interrupt, - ) -> Result; - - /// Adds the given associations to the index. - /// - /// Returns the set of keywords added as new keys to the index. - async fn add( - &self, - key: &UserKey, - label: &Label, - associations: IndexedValueToKeywordsMap, - ) -> Result; - - /// Removes the given associations from the index. - /// - /// This operation actually adds the negation of the given associations to the index, - /// effectively increasing the index size. The compact operation is in charge of removing - /// associations that have been negated. - /// - /// Returns the set of keywords added as new keys to the index. - async fn delete( - &self, - key: &UserKey, - label: &Label, - associations: IndexedValueToKeywordsMap, - ) -> Result; - - /// Compacts a portion of the index. - /// - /// It re-encrypts the entire Entry Table which allows to reset the knowledge of the index - /// acquired by an attacker. To this effect at least either the key or the label needs to be - /// changed. - /// - /// It partially compacts and re-encrypts the Chain Table. The compacting operation: - /// - removes duplicated associations; - /// - removes deleted associations; - /// - removes obsolete indexed data; - /// - ensures the padding is minimal. - /// - /// The `data_filter` is called with batches of the data read from the index. Only the data - /// returned by it is indexed back. - /// - /// The entire index is statistically guaranteed to be compacted after calling this operation - /// `n_compact_to_full` times. For example, if one is passed, the entire index will be - /// compacted at once. If ten is passed, the entire index should have been compacted after the - /// tenth call. - async fn compact< - F: Future, String>>, - Filter: Fn(HashSet) -> F, - >( - &self, - old_key: &UserKey, - new_key: &UserKey, - old_label: &Label, - new_label: &Label, - compacting_rate: f64, - data_filter: &Filter, - ) -> Result<(), Self::Error>; -} - -/// Findex type implements the Findex algorithm. -#[derive(Debug)] -pub struct Findex< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc>, - ChainTable: DxEnc>, -> { - pub findex_graph: FindexGraph, - rng: Arc>, -} - -#[async_trait(?Send)] -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc> + TokenDump>, - ChainTable: DxEnc>, - > Index for Findex -{ - type Error = Error; - - fn new(et: EntryTable, ct: ChainTable) -> Self { - Self { - findex_graph: FindexGraph::new(et, ct), - rng: Arc::new(Mutex::new(CsRng::from_entropy())), - } - } - - fn keygen(&self) -> UserKey { - UserKey::new(&mut *self.rng.lock().expect("could not lock mutex")) - } - - #[instrument(ret(Display), err, skip_all)] - async fn search< - F: Future>, - Interrupt: Fn(HashMap>>) -> F, - >( - &self, - key: &UserKey, - label: &Label, - keywords: Keywords, - interrupt: &Interrupt, - ) -> Result { - trace!("search: entering: label: {label}"); - trace!("search: entering: keywords: {keywords}"); - // TODO: avoid this copy - let mut seed = - as GxEnc>::Seed::default(); - seed.as_mut().copy_from_slice(key.as_bytes()); - let key = self.findex_graph.derive_keys(&seed); - - let graph = self - .findex_graph - .get(&key, keywords.clone().into(), label, interrupt) - .await?; - - let res = keywords - .into_iter() - .map(|tag| { - let data = self.findex_graph.walk(&graph, &tag, &mut HashSet::new()); - (tag, data) - }) - .collect(); - - Ok(res) - } - - #[instrument(ret(Display), err, skip_all)] - async fn add( - &self, - key: &UserKey, - label: &Label, - additions: IndexedValueToKeywordsMap, - ) -> Result { - trace!("add: entering: label: {label}"); - trace!("add: entering: additions: {additions}"); - // TODO: avoid this copy - let mut seed = - as GxEnc>::Seed::default(); - seed.as_mut().copy_from_slice(key.as_bytes()); - let key = self.findex_graph.derive_keys(&seed); - - let mut modifications = HashMap::<_, Vec<_>>::new(); - for (value, keywords) in additions { - for keyword in keywords { - modifications - .entry(keyword) - .or_default() - .push((Operation::Addition, value.clone())); - } - } - - Ok(Keywords::from( - self.findex_graph - .insert(self.rng.clone(), &key, modifications, label) - .await?, - )) - } - - #[instrument(ret(Display), err, skip_all)] - async fn delete( - &self, - key: &UserKey, - label: &Label, - deletions: IndexedValueToKeywordsMap, - ) -> Result { - trace!("delete: entering: label: {label}"); - trace!("delete: entering: deletions: {deletions}"); - // TODO: avoid this copy - let mut seed = - as GxEnc>::Seed::default(); - seed.as_mut().copy_from_slice(key.as_bytes()); - let key = self.findex_graph.derive_keys(&seed); - - let mut modifications = HashMap::<_, Vec<_>>::new(); - for (value, keywords) in deletions { - for keyword in keywords { - modifications - .entry(keyword) - .or_default() - .push((Operation::Deletion, value.clone())); - } - } - - Ok(Keywords::from( - self.findex_graph - .insert(self.rng.clone(), &key, modifications, label) - .await?, - )) - } - - /// Process the entire Entry Table by batch. Compact a random portion of - /// the associated chains such that the Chain Table is entirely compacted - /// after `n_compact_to_full` operations in average. A new token is - /// generated for each entry and the entries are re-encrypted using the - /// `new_key` and the `new_label`. - /// - /// A compact operation on a given chain: - /// - fetches and decrypts the chain; - /// - simplifies additions/deletions of the same values; - /// - writes the chains without internal padding; - /// - generates new keys for this chain - /// - encrypts the new chain using the new key - /// - /// The size of the batches is - /// [`COMPACT_BATCH_SIZE`](Self::COMPACT_BATCH_SIZE). - #[instrument(ret, err, skip_all)] - async fn compact< - F: Future, String>>, - Filter: Fn(HashSet) -> F, - >( - &self, - old_key: &UserKey, - new_key: &UserKey, - old_label: &Label, - new_label: &Label, - compacting_rate: f64, - data_filter: &Filter, - ) -> Result<(), Error> { - trace!("compact: entering: old_label: {old_label}"); - trace!("compact: entering: new_label: {new_label}"); - if (old_key == new_key) && (old_label == new_label) { - return Err(Error::Crypto( - "at least one from the new key or the new label should be changed during the \ - compact operation" - .to_string(), - )); - } - - let mut new_seed = - as GxEnc>::Seed::default(); - new_seed.as_mut().copy_from_slice(new_key); - let new_key = self.findex_graph.derive_keys(&new_seed); - - let mut old_seed = - as GxEnc>::Seed::default(); - old_seed.as_mut().copy_from_slice(old_key); - let old_key = self.findex_graph.derive_keys(&old_seed); - - let entry_tokens = self.findex_graph.list_indexed_encrypted_tags().await?; - - let entries_to_compact = self - .select_random_tokens( - self.get_compact_line_number(entry_tokens.len(), compacting_rate), - entry_tokens.as_slice(), - ) - .into(); - - for i in 0..entry_tokens.len() / Self::COMPACT_BATCH_SIZE { - self.compact_batch( - &old_key, - &new_key, - new_label, - &entries_to_compact, - entry_tokens[i * Self::COMPACT_BATCH_SIZE..(i + 1) * Self::COMPACT_BATCH_SIZE] - .iter() - .copied() - .collect(), - data_filter, - ) - .await?; - } - - self.compact_batch( - &old_key, - &new_key, - new_label, - &entries_to_compact, - entry_tokens - [(entry_tokens.len() / Self::COMPACT_BATCH_SIZE) * Self::COMPACT_BATCH_SIZE..] - .iter() - .copied() - .collect(), - data_filter, - ) - .await?; - - Ok(()) - } -} - -impl< - UserError: DbInterfaceErrorTrait, - EntryTable: DxEnc> + TokenDump>, - ChainTable: DxEnc>, - > Findex -{ - /// Number of items to compact at once. - /// - /// Given that an entry is EB bytes long and that a link is LB bytes long, - /// the memory used by the compact operation is: - /// - /// N * 32 + BS * EB + f * BS * LB - const COMPACT_BATCH_SIZE: usize = 1_000_000; - - /// Draw `n` tokens at random among the given `tokens`. The same token may - /// be drawn several times, thus the number of tokens returned may be - /// lower than `n`. - /// - /// TODO: update the formula used to select the number of lines to compact. - fn select_random_tokens(&self, n: usize, tokens: &[Token]) -> HashSet { - if tokens.len() <= n { - return tokens.iter().copied().collect(); - } - - let mut rng = self.rng.lock().expect("could not lock mutex"); - let mut res = HashSet::with_capacity(n); - for _ in 0..n { - // In order to draw a random element from the set, draw a random `u64` and use - // it modulo the length of the set. This is not perfectly uniform but should be - // enough in practice. - let index = (rng.next_u64() % tokens.len() as u64) as usize; - res.insert(tokens[index]); - } - res - } - - /// Returns the expected number of draws per compact operation such that all - /// Entry Table tokens are drawn after `n_compact_to_full` such operation. - fn get_compact_line_number(&self, entry_table_length: usize, compacting_rate: f64) -> usize { - // [Euler's gamma constant](https://en.wikipedia.org/wiki/Euler%E2%80%93Mascheroni_constant). - const GAMMA: f64 = 0.5772; - - let length = entry_table_length as f64; - // Number of draws needed to get the whole batch, see the - // [coupon collector's problem](https://en.wikipedia.org/wiki/Coupon_collector%27s_problem). - let n_draws = length.mul_add(length.log2() + GAMMA, 0.5); - // Split this number among the given number of compact operations. - (n_draws * compacting_rate) as usize - } - - #[instrument(ret, err, skip_all)] - async fn compact_batch< - F: Future, String>>, - Filter: Fn(HashSet) -> F, - >( - &self, - old_key: & as GxEnc>::Key, - new_key: & as GxEnc>::Key, - new_label: &Label, - tokens_to_compact: &Tokens, - tokens_to_fetch: Tokens, - data_filter: &Filter, - ) -> Result<(), Error> { - trace!("compact_batch: entering: new_label: {new_label}"); - trace!("compact_batch: entering: tokens_to_compact: {tokens_to_compact}"); - trace!("compact_batch: entering: tokens_to_fetch: {tokens_to_fetch}"); - let (indexed_values, data) = self - .findex_graph - .prepare_compact::(old_key, tokens_to_fetch.into(), tokens_to_compact) - .await?; - - let indexed_data = indexed_values - .values() - .flatten() - .filter_map(IndexedValue::get_data) - .cloned() - .collect(); - - let remaining_data = data_filter(indexed_data) - .await - .map_err(>::Error::Filter)?; - - let remaining_values = indexed_values - .into_iter() - .map(|(entry_token, associated_values)| { - let remaining_values = associated_values - .into_iter() - .filter(|value| { - // Filter out obsolete data. - value - .get_data() - .map_or(true, |data| remaining_data.contains(data)) - }) - .collect::>(); - (entry_token, remaining_values) - }) - .collect::>(); - - self.findex_graph - .complete_compacting(self.rng.clone(), new_key, new_label, remaining_values, data) - .await - } -} diff --git a/src/index/structs.rs b/src/index/structs.rs deleted file mode 100644 index b1db9126..00000000 --- a/src/index/structs.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Structures used by the `Index` interface of `Findex`. - -use std::{ - collections::{HashMap, HashSet}, - fmt::Display, - ops::{Deref, DerefMut}, -}; - -use cosmian_crypto_core::{reexport::rand_core::CryptoRngCore, SymmetricKey}; - -use crate::{IndexedValue, USER_KEY_LENGTH}; - -pub type UserKey = SymmetricKey; - -/// The label is used to provide additional public information to the hash -/// algorithm when generating Entry Table UIDs. -#[must_use] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Label(Vec); - -impl Label { - /// Generates a new random label of 32 bytes. - /// - /// - `rng` : random number generator - pub fn random(rng: &mut impl CryptoRngCore) -> Self { - let mut bytes = vec![0; 32]; - rng.fill_bytes(&mut bytes); - Self(bytes) - } -} - -impl_byte_vector!(Label); - -/// A [`Keyword`] is a byte vector used to index other values. -#[must_use] -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Keyword(Vec); - -impl_byte_vector!(Keyword); - -/// A [`Data`] is an arbitrary byte-string that is indexed under some keyword. -/// -/// In a typical use case, it would represent a database UID and would be indexed under the -/// keywords associated to the corresponding database value. -#[must_use] -#[derive(Clone, Debug, Hash, Default, PartialEq, Eq)] -pub struct Data(Vec); - -impl_byte_vector!(Data); - -#[derive(Debug, Clone, Default, Eq, PartialEq)] -pub struct Keywords(HashSet); - -impl Deref for Keywords { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Keywords { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromIterator<&'static str> for Keywords { - fn from_iter>(iter: T) -> Self { - Self(HashSet::from_iter(iter.into_iter().map(Keyword::from))) - } -} - -impl FromIterator for Keywords { - fn from_iter>(iter: T) -> Self { - Self(HashSet::from_iter(iter)) - } -} - -impl IntoIterator for Keywords { - type IntoIter = <::Target as IntoIterator>::IntoIter; - type Item = Keyword; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl Display for Keywords { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Keywords: [")?; - for keyword in &self.0 { - writeln!(f, " {keyword},")?; - } - writeln!(f, "]") - } -} - -impl From> for Keywords { - fn from(set: HashSet) -> Self { - Self(set) - } -} - -impl From for HashSet { - fn from(value: Keywords) -> Self { - value.0 - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct KeywordToDataMap(HashMap>); - -impl Deref for KeywordToDataMap { - type Target = HashMap>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for KeywordToDataMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Display for KeywordToDataMap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Keyword to Data map: {{")?; - for (keyword, data) in &self.0 { - writeln!(f, " '{keyword}': [")?; - for data in data { - writeln!(f, " '{data}',")?; - } - writeln!(f, " ]")?; - } - write!(f, "}}") - } -} - -impl FromIterator<(Keyword, HashSet)> for KeywordToDataMap { - fn from_iter)>>(iter: T) -> Self { - Self(HashMap::from_iter(iter)) - } -} - -impl IntoIterator for KeywordToDataMap { - type IntoIter = <::Target as IntoIterator>::IntoIter; - type Item = (Keyword, HashSet); - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl From>> for KeywordToDataMap { - fn from(map: HashMap>) -> Self { - Self(map) - } -} - -impl From for HashMap> { - fn from(value: KeywordToDataMap) -> Self { - value.0 - } -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct IndexedValueToKeywordsMap(HashMap, Keywords>); - -impl Deref for IndexedValueToKeywordsMap { - type Target = HashMap, Keywords>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for IndexedValueToKeywordsMap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "IndexedValue to Keyword map: {{")?; - for (iv, keywords) in &self.0 { - writeln!(f, "'{iv}': {keywords},")?; - } - write!(f, "}}") - } -} - -impl IntoIterator for IndexedValueToKeywordsMap { - type IntoIter = <::Target as IntoIterator>::IntoIter; - type Item = (IndexedValue, Keywords); - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl FromIterator<(IndexedValue, Keywords)> for IndexedValueToKeywordsMap { - fn from_iter, Keywords)>>(iter: T) -> Self { - Self(HashMap::from_iter(iter)) - } -} - -impl FromIterator<(IndexedValue, HashSet)> for IndexedValueToKeywordsMap { - fn from_iter, HashSet)>>( - iter: T, - ) -> Self { - Self(HashMap::from_iter( - iter.into_iter().map(|(k, v)| (k, Keywords::from(v))), - )) - } -} - -impl From, Keywords>> for IndexedValueToKeywordsMap { - fn from(map: HashMap, Keywords>) -> Self { - Self(map) - } -} - -impl From, HashSet>> for IndexedValueToKeywordsMap { - fn from(map: HashMap, HashSet>) -> Self { - Self( - map.into_iter() - .map(|(iv, k)| (iv, Keywords::from(k))) - .collect(), - ) - } -} - -impl From<[(IndexedValue, Keywords); N]> - for IndexedValueToKeywordsMap -{ - fn from(value: [(IndexedValue, Keywords); N]) -> Self { - Self(HashMap::from(value)) - } -} diff --git a/src/kv.rs b/src/kv.rs new file mode 100644 index 00000000..a0393acd --- /dev/null +++ b/src/kv.rs @@ -0,0 +1,99 @@ +use std::{ + collections::HashMap, + fmt::{Debug, Display}, + hash::Hash, + sync::{Arc, Mutex}, +}; + +use crate::stm::Stm; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MemoryError; + +impl Display for MemoryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Memory Error") + } +} + +impl std::error::Error for MemoryError {} + +pub struct KvStore(Arc>>); + +impl KvStore { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(HashMap::new()))) + } +} + +impl Stm for KvStore { + type Address = Address; + + type Word = Value; + + type Error = MemoryError; + + fn batch_read(&self, a: Vec
) -> Result)>, Self::Error> { + let store = &mut *self.0.lock().expect("poisoned lock"); + Ok(a.into_iter() + .map(|k| { + let v = store.get(&k).cloned(); + //println!("get\t{:?} -> {:?}", k, v); + (k, v) + }) + .collect()) + } + + fn guarded_write( + &self, + guard: (Self::Address, Option), + bindings: Vec<(Self::Address, Self::Word)>, + ) -> Result, Self::Error> { + let store = &mut *self.0.lock().expect("poisoned lock"); + let (a, old) = guard; + let cur = store.get(&a); + if old.as_ref() == cur { + for (k, v) in bindings { + println!("set\t{:?}", k); + store.insert(k, v); + } + } + let new = store.get(&a).cloned(); + Ok(new) + } +} + +#[cfg(test)] +mod tests { + use crate::stm::Stm; + + use super::KvStore; + + /// Ensures a transaction can express an vector push operation: + /// - the counter is correctly incremented and all values are written; + /// - using the wrong value in the guard fails the operation and returns the current value. + #[test] + fn test_vector_push() { + let kv = KvStore::::new(); + + assert_eq!( + kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)]) + .unwrap(), + Some(2) + ); + assert_eq!( + kv.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)]) + .unwrap(), + Some(2) + ); + assert_eq!( + kv.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)]) + .unwrap(), + Some(4) + ); + assert_eq!( + vec![(1, Some(1)), (2, Some(1)), (3, Some(3)), (4, Some(3))], + kv.batch_read(vec![1, 2, 3, 4]).unwrap(), + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index f72e9d2d..7fa60e33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,314 +1,10 @@ -//! Findex is a cryptographic algorithm allowing to securely maintain an encrypted index. -//! -//! It uses a generic Dictionary Encryption Scheme (Dx-Enc) as building block to implement a -//! Multi-Map Encryption Scheme (MM-Enc). A Graph Encryption Scheme (Gx-Enc) is then built on top -//! of the MM-Enc scheme and finally an `Index` trait built on top of this Gx-Enc scheme allows -//! indexing both `Data` and `Keyword`s. -//! -//! The `Index` traits is not a cryptographic one. It is used to simplify the interface and to hide -//! the details of the cryptographic implementation when it is possible. +#![allow(dead_code)] +#![allow(clippy::type_complexity)] -// Macro declarations should come first. -#[macro_use] -pub mod macros; - -mod edx; +mod address; mod error; -mod findex_graph; -mod findex_mm; -mod index; -mod parameters; - -#[cfg(any(test, feature = "in_memory"))] -pub use edx::in_memory::{InMemoryDb, InMemoryDbError}; -pub use edx::{ - chain_table::ChainTable, entry_table::EntryTable, DbInterface, DxEnc, EncryptedValue, Token, - TokenToEncryptedValueMap, TokenWithEncryptedValueList, Tokens, -}; -pub use error::{CoreError, DbInterfaceErrorTrait, Error}; -pub use findex_graph::IndexedValue; -pub use findex_mm::{ENTRY_LENGTH, LINK_LENGTH}; -pub use index::{ - Data, Findex, Index, IndexedValueToKeywordsMap, Keyword, KeywordToDataMap, Keywords, Label, - UserKey, -}; -pub use parameters::*; - -#[cfg(test)] -mod example { - use std::collections::HashSet; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, RandomFixedSizeCBytes}; - - use crate::{ - ChainTable, Data, DxEnc, EntryTable, Findex, InMemoryDb, Index, IndexedValue, - IndexedValueToKeywordsMap, Keyword, KeywordToDataMap, Keywords, Label, UserKey, - }; - - #[actix_rt::test] - async fn index_and_search() { - /* - * Findex instantiation. - */ - - let mut rng = CsRng::from_entropy(); - // Let's create a new key for our index. - let key = UserKey::new(&mut rng); - // Findex uses a public label with the private key. Let's generate a new label. - let label = Label::from("My public label"); - - // Let's create a new index using the provided Entry and Chain table implementation and the - // in-memory EDX implementation provided for test purpose. - let index = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - //////////////////////////////////////////////////////////////////////////////// - // // - // Let's associate `loc1` to `kwd1`, `loc2` to `kwd2` and `kwd2` to `kwd1`. // - // The future state of the index can be represented as a JSON: // - // // - // ```json // - // { // - // 'kwd1' : ['loc1', 'kwd2'], // - // 'kwd2' : ['loc2'], // - // } // - // ``` // - // // - //////////////////////////////////////////////////////////////////////////////// - - let kwd1 = Keyword::from("Keyword 1"); - let kwd2 = Keyword::from("Keyword 2"); - let loc1 = Data::from("Location 1"); - let loc2 = Data::from("Location 2"); - - let res = index - .add( - &key, - &label, - IndexedValueToKeywordsMap::from_iter([ - ( - IndexedValue::Data(loc1.clone()), - HashSet::from_iter([kwd1.clone()]), - ), - ( - IndexedValue::Data(loc2.clone()), - HashSet::from_iter([kwd2.clone()]), - ), - ( - IndexedValue::Pointer(kwd2.clone()), - HashSet::from_iter([kwd1.clone()]), - ), - ]), - ) - .await - .expect("Error while indexing additions."); - - // Two new keywords were added to the index. - assert_eq!(2, res.len()); - - let res = index - .search( - &key, - &label, - Keywords::from_iter([kwd1.clone()]), - &|_| async { Ok(false) }, - ) - .await - .expect("Error while searching."); - - // Searching for `kwd1` also retrieves `loc2` since `kwd2` is associated to `kwd1` and that - // Findex search is recursive. - assert_eq!( - res, - KeywordToDataMap::from_iter([( - kwd1.clone(), - HashSet::from_iter([loc1.clone(), loc2.clone()]) - )]) - ); - - //////////////////////////////////////////////////////////////////////////////// - // // - // Let's delete the association `kwd1`->`kwd2`. This actually associates the // - // negation of `kwd2` to `kwd1`. // - // // - // ```json // - // { // - // 'kwd1' : ['loc1', 'kwd2', !'kwd2'], // - // 'kwd2' : ['loc2'], // - // } // - // ``` // - // // - //////////////////////////////////////////////////////////////////////////////// - - let res = index - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from_iter([( - IndexedValue::Pointer(kwd2.clone()), - HashSet::from_iter([kwd1.clone()]), - )]), - ) - .await - .expect("Error while indexing deletions."); - - // No new keyword were added to the index. - assert_eq!(0, res.len()); - - let res = index - .search( - &key, - &label, - Keywords::from_iter([kwd1.clone()]), - &|_| async { Ok(false) }, - ) - .await - .expect("Error while searching."); - - // Searching for `kwd1` no longer retrieves `loc2`. - assert_eq!( - res, - KeywordToDataMap::from_iter([(kwd1, HashSet::from_iter([loc1.clone()]))]) - ); - - //////////////////////////////////////////////////////////////////////////////// - // // - // Let's compact the index in order to collapse the negation. // - // // - // ```json // - // { // - // 'kwd1' : ['loc1'], // - // 'kwd2' : ['loc2'], // - // } // - // ``` // - // // - //////////////////////////////////////////////////////////////////////////////// - - // Before compacting, the Entry Table holds 2 lines since two keywords were indexed. - let et_length = index.findex_graph.findex_mm.entry_table.len(); - assert_eq!(2, et_length); - - // Before compacting, the Entry Table holds 3 lines since four associations were indexed - // but two of them were indexed for the same keyword in the same `add` operations and the - // indexed values are small enough to hold in the same line. - let ct_length = index.findex_graph.findex_mm.chain_table.len(); - assert_eq!(3, ct_length); - - let res = index - .compact(&key, &key, &label, &label, 1., &|res| async { Ok(res) }) - .await; - - // Ooops we forgot to renew either the key or the label! - assert!(res.is_err()); - - // A new label is easier to propagate since this is public information. - let new_label = Label::from("second label"); - - index - .compact(&key, &key, &label, &new_label, 1f64, &|res| async { - Ok(res) - }) - .await - .unwrap(); - - // `new_label` is the new `label`. - let label = new_label; - - // After compacting, the Entry Table still holds 2 lines since each indexed keyword still - // holds at least one association. - let et_length = index.findex_graph.findex_mm.entry_table.len(); - assert_eq!(2, et_length); - - // After compacting, the Chain Table holds 2 lines since the two associations - // `kwd1`->`kwd2` and `kwd1`->!`kwd2` collapsed. - let ct_length = index.findex_graph.findex_mm.chain_table.len(); - assert_eq!(2, ct_length); - - //////////////////////////////////////////////////////////////////////////////// - // // - // Let's delete the association `loc2`->`kwd2` and compact the index in // - // order to collapse the negation. Since `kwd2` indexes no more keyword, // - // it should be removed from the index: // - // // - // ```json // - // { // - // 'kwd1' : ['loc1'], // - // } // - // ``` // - // // - //////////////////////////////////////////////////////////////////////////////// - - index - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from_iter([( - IndexedValue::Data(loc2), - HashSet::from_iter([kwd2.clone()]), - )]), - ) - .await - .expect("Error while indexing deletions."); - - // The Entry Table still holds 2 lines since no more keywords were indexed. - let et_length = index.findex_graph.findex_mm.entry_table.len(); - assert_eq!(2, et_length); - - // The Chain Table holds 3 lines since a new association was indexed. - let ct_length = index.findex_graph.findex_mm.chain_table.len(); - assert_eq!(3, ct_length); - - let new_label = Label::from("third label"); - index - .compact(&key, &key, &label, &new_label, 1f64, &|res| async { - Ok(res) - }) - .await - .unwrap(); - let label = new_label; - - // The Entry Table now holds only 1 line since `kwd2` was not associated to any indexed - // value anymore. - let et_length = index.findex_graph.findex_mm.entry_table.len(); - assert_eq!(1, et_length); - - // The Chain Table holds 1 lines since a two associations collapsed. - let ct_length = index.findex_graph.findex_mm.chain_table.len(); - assert_eq!(1, ct_length); - - //////////////////////////////////////////////////////////////////////////////// - // // - // It is possible to filter out indexed values from the index during the // - // compact operation. This is useful when indexed values become obsolete // - // but the index was not updated. A `data_filter` callback can be given to // - // the compact operation. It is fed with the indexed values read during // - // the compact operation. Only those returned are indexed back. // - // // - // In this example, the `loc1` value will be filtered out. The index should // - // then be empty since the `kwd1` will not be associated to any value. // - // // - // ```json // - // {} // - // ``` // - // // - //////////////////////////////////////////////////////////////////////////////// - - let new_label = Label::from("fourth label"); - index - .compact(&key, &key, &label, &new_label, 1f64, &|data| async { - let remaining_data = data.into_iter().filter(|v| v != &loc1).collect(); - Ok(remaining_data) - }) - .await - .unwrap(); - let _label = new_label; - - let et_length = index.findex_graph.findex_mm.entry_table.len(); - assert_eq!(0, et_length); +mod kv; +mod obf; +mod stm; - let ct_length = index.findex_graph.findex_mm.chain_table.len(); - assert_eq!(0, ct_length); - } -} +pub use stm::Stm; diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index a08b89b3..00000000 --- a/src/macros.rs +++ /dev/null @@ -1,84 +0,0 @@ -pub use core::ops::{Deref, DerefMut}; - -pub use tiny_keccak::{self, Hasher, IntoXof, Kmac, KmacXof, Xof}; - -/// Hashes the given bytes to the desired length using the KMAC algorithm and -/// the given key. -/// -/// - `length` : length of the generated output -/// - `key` : KMAC key -/// - `bytes` : bytes to hash -#[macro_export] -macro_rules! kmac { - ($length: ident, $key: expr, $($bytes: expr),+) => { - { - let mut kmac = $crate::macros::tiny_keccak::Kmac::v256($key, b""); - $( - <$crate::macros::Kmac as $crate::macros::Hasher>::update(&mut kmac, $bytes); - )* - let mut xof = <$crate::macros::Kmac as $crate::macros::IntoXof>::into_xof(kmac); - let mut res = [0; $length]; - <$crate::macros::KmacXof as $crate::macros::Xof>::squeeze(&mut xof, &mut res); - res - } - }; -} - -/// Implements the functionalities of a byte-vector. -/// -/// # Parameters -/// -/// - `type_name` : name of the byte-vector type -macro_rules! impl_byte_vector { - ($type_name:ty) => { - impl AsRef<[u8]> for $type_name { - fn as_ref(&self) -> &[u8] { - &self.0 - } - } - - impl $crate::macros::Deref for $type_name { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl $crate::macros::DerefMut for $type_name { - fn deref_mut(&mut self) -> &mut ::Target { - &mut self.0 - } - } - - impl<'a> From<&'a [u8]> for $type_name { - fn from(bytes: &'a [u8]) -> Self { - Self(bytes.to_vec()) - } - } - - impl From> for $type_name { - fn from(bytes: Vec) -> Self { - Self(bytes) - } - } - - impl From<&str> for $type_name { - fn from(bytes: &str) -> Self { - bytes.as_bytes().into() - } - } - - impl From<$type_name> for Vec { - fn from(var: $type_name) -> Self { - var.0 - } - } - - impl std::fmt::Display for $type_name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", String::from_utf8_lossy(&self.0)) - } - } - }; -} diff --git a/src/obf.rs b/src/obf.rs new file mode 100644 index 00000000..a8e30a73 --- /dev/null +++ b/src/obf.rs @@ -0,0 +1,273 @@ +use std::{ + cmp::Ordering, + collections::HashMap, + ops::DerefMut, + sync::{Mutex, MutexGuard}, +}; + +use crate::{address::Address, error::Error, Stm}; +use aes::{ + cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, + Aes256, +}; +use cosmian_crypto_core::{ + reexport::rand_core::RngCore, Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, + RandomFixedSizeCBytes, Secret, SymmetricKey, +}; + +const ADDRESS_LENGTH: usize = 16; +const KEY_LENGTH: usize = 32; + +pub struct ObfuscationLayer, Word = Vec>> { + permutation_key: SymmetricKey, + encryption_key: SymmetricKey<32>, + cache: Mutex, Vec), Vec>>, + rng: Mutex, + stm: Memory, +} + +impl, Word = Vec>> ObfuscationLayer { + pub fn new(seed: Secret, rng: CsRng, stm: Memory) -> Self { + let permutation_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); + let encryption_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); + Self { + permutation_key, + encryption_key, + cache: Mutex::new(HashMap::new()), + rng: Mutex::new(rng), + stm, + } + } + + pub fn rng(&self) -> MutexGuard { + self.rng.lock().expect("poisoned lock") + } + + /// Shuffles the given list of values. + fn shuffle(&self, mut v: Vec) -> Vec { + v.sort_by(|_, _| { + if self.rng().next_u32() % 2 == 0 { + Ordering::Less + } else { + Ordering::Greater + } + }); + v + } + + pub fn encrypt( + &self, + ptx: &[u8], + tok: &Address, + ) -> Result, Error> { + let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); + let ctx = Aes256Gcm::new(&self.encryption_key) + .encrypt(&nonce, ptx, Some(tok)) + .map_err(Error::Encryption)?; + let ctx = [nonce.as_bytes(), &ctx].concat(); + Ok(ctx) + } + + pub fn decrypt( + &self, + ctx: &[u8], + tok: &Address, + ) -> Result, Error> { + if ctx.len() < Aes256Gcm::NONCE_LENGTH { + return Err(Error::Parsing("ciphertext too small".to_string())); + } + let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) + .map_err(|e| Error::Parsing(e.to_string()))?; + let ptx = Aes256Gcm::new(&self.encryption_key) + .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(tok)) + .map_err(Error::Encryption)?; + Ok(ptx) + } + + pub fn permute(&self, mut a: Address) -> Address { + let e = Aes256::new(GenericArray::from_slice(&self.permutation_key)); + e.encrypt_block(GenericArray::from_mut_slice(&mut a)); + a + } + + pub fn bind(&self, k: (Address, Vec), v: Vec) { + self.cache + .lock() + .expect("poisoned lock") + .deref_mut() + .insert(k, v); + } + + pub fn find(&self, k: &(Address, Vec)) -> Option> { + self.cache + .lock() + .expect("poisoned lock") + .deref_mut() + .get(k) + .cloned() + } +} + +impl< + Memory: Stm
, Word = Vec>, // use a `Vec` because `const` generics + // are not allowed in `const` operations + > Stm for ObfuscationLayer +{ + type Address = Address; + + type Word = Memory::Word; + + type Error = Error; + + fn batch_read( + &self, + addresses: Vec, + ) -> Result)>, Self::Error> { + let addresses = self.shuffle(addresses); + let tokens = addresses.iter().cloned().map(|a| self.permute(a)).collect(); + let bindings = self.stm.batch_read(tokens)?; + addresses + .into_iter() + .zip(bindings.iter()) + .map(|(a, (tok, ctx))| { + ctx.as_deref() + .map(|ctx| self.decrypt(ctx, tok)) + .transpose() + .map(|maybe_ctx| (a, maybe_ctx)) + }) + .collect() + } + + fn guarded_write( + &self, + guard: (Self::Address, Option), + bindings: Vec<(Self::Address, Self::Word)>, + ) -> Result, Self::Error> { + let (a, v) = guard; + let tok = self.permute(a.clone()); + let old = v.and_then(|v| self.find(&(a.clone(), v))); + + let bindings = bindings + .into_iter() + .map(|(a, v)| { + let tok = self.permute(a); + self.encrypt(&v, &tok).map(|ctx| (tok, ctx)) + }) + .collect::, _>>()?; + + let ctx = self.stm.guarded_write((tok.clone(), old), bindings)?; + + if let Some(ctx) = ctx { + let ptx = self.decrypt(&ctx, &tok)?; + self.bind((a, ptx.clone()), ctx); + Ok(Some(ptx)) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + + use crate::{ + address::Address, + kv::KvStore, + obf::{ObfuscationLayer, ADDRESS_LENGTH}, + stm::Stm, + }; + + #[test] + fn test_encrypt_decrypt() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::new(); + let obf = ObfuscationLayer::new(seed, rng.clone(), kv); + let tok = Address::::random(&mut rng); + let ptx = vec![1]; + let ctx = obf.encrypt(&ptx, &tok).unwrap(); + let res = obf.decrypt(&ctx, &tok).unwrap(); + assert_eq!(ptx.len(), res.len()); + assert_eq!(ptx, res); + } + + /// Ensures a transaction can express an vector push operation: + /// - the counter is correctly incremented and all values are written; + /// - using the wrong value in the guard fails the operation and returns the current value. + #[test] + fn test_vector_push() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::new(); + let obf = ObfuscationLayer::new(seed, rng.clone(), kv); + + let header_addr = Address::::random(&mut rng); + + let val_addr_1 = Address::::random(&mut rng); + let val_addr_2 = Address::::random(&mut rng); + let val_addr_3 = Address::::random(&mut rng); + let val_addr_4 = Address::::random(&mut rng); + + assert_eq!( + obf.guarded_write( + (header_addr.clone(), None), + vec![ + (header_addr.clone(), vec![2]), + (val_addr_1.clone(), vec![1]), + (val_addr_2.clone(), vec![1]) + ] + ) + .unwrap(), + Some(vec![2]) + ); + + assert_eq!( + obf.guarded_write( + (header_addr.clone(), None), + vec![ + (header_addr.clone(), vec![2]), + (val_addr_1.clone(), vec![3]), + (val_addr_2.clone(), vec![3]) + ] + ) + .unwrap(), + Some(vec![2]) + ); + + assert_eq!( + obf.guarded_write( + (header_addr.clone(), Some(vec![2])), + vec![ + (header_addr.clone(), vec![4]), + (val_addr_3.clone(), vec![2]), + (val_addr_4.clone(), vec![2]) + ] + ) + .unwrap(), + Some(vec![4]) + ); + + assert_eq!( + HashSet::<(Address, Option>)>::from_iter([ + (header_addr.clone(), Some(vec![4])), + (val_addr_1.clone(), Some(vec![1])), + (val_addr_2.clone(), Some(vec![1])), + (val_addr_3.clone(), Some(vec![2])), + (val_addr_4.clone(), Some(vec![2])) + ]), + HashSet::from_iter( + obf.batch_read(vec![ + header_addr, + val_addr_1, + val_addr_2, + val_addr_3, + val_addr_4 + ]) + .unwrap() + ), + ) + } +} diff --git a/src/parameters.rs b/src/parameters.rs deleted file mode 100644 index 9330915a..00000000 --- a/src/parameters.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Defines parameters used by the Findex SSE scheme. These are parameters that -//! are not destined to be changed from one instantiation to another. Most of -//! them are linked to security considerations or scheme correctness. - -use cosmian_crypto_core::Aes256Gcm; - -/// Size of the Findex tag hash used. Only collision resistance is needed: 128 -/// bits should be enough. -pub const HASH_LENGTH: usize = 32; - -/// Seed used to derive the keys. Only collision resistance is needed: 128 bits -/// should be enough. -pub const SEED_LENGTH: usize = 16; - -/// Size of the token used. It is 256 bits in order to allow more than 80 bits -/// of post-quantum resistance. -pub(crate) const TOKEN_LENGTH: usize = 32; - -/// Length of the user key. -pub const USER_KEY_LENGTH: usize = 16; - -/// Length of the symmetric encryption keys used. -pub const SYM_KEY_LENGTH: usize = Aes256Gcm::KEY_LENGTH; - -/// Length of the MAC tags used. -pub const MAC_LENGTH: usize = Aes256Gcm::MAC_LENGTH; - -/// Length of the nonces used. -pub const NONCE_LENGTH: usize = Aes256Gcm::NONCE_LENGTH; - -/// Length of the blocks stored in the Chain Table. -pub const BLOCK_LENGTH: usize = 16; - -/// Number of blocks stored per line of the Chain Table. -pub const LINE_WIDTH: usize = 5; diff --git a/src/stm.rs b/src/stm.rs new file mode 100644 index 00000000..621a7a06 --- /dev/null +++ b/src/stm.rs @@ -0,0 +1,24 @@ +pub trait Stm { + /// Address space. + type Address; + + /// Word space. + type Word; + + /// Memory error. + type Error: std::error::Error; + + /// Reads the words bound to the given addresses. + fn batch_read( + &self, + a: Vec, + ) -> Result)>, Self::Error>; + + /// Adds the given memory bindings if the guard binding is stored. + /// Returns the value of the guarded word after the writes. + fn guarded_write( + &self, + guard: (Self::Address, Option), + bindings: Vec<(Self::Address, Self::Word)>, + ) -> Result, Self::Error>; +} diff --git a/tests/non_regression.rs b/tests/non_regression.rs deleted file mode 100644 index 23aedf5d..00000000 --- a/tests/non_regression.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fs::File, - io::{BufRead, BufReader, BufWriter, Write}, -}; - -use cosmian_crypto_core::{ - bytes_ser_de::{Deserializer, Serializer}, - FixedSizeCBytes, RandomFixedSizeCBytes, -}; -use cosmian_findex::{ - ChainTable, Data, DxEnc, EntryTable, Error, Findex, InMemoryDb, InMemoryDbError, Index, - IndexedValue, IndexedValueToKeywordsMap, Keyword, Keywords, Label, UserKey, ENTRY_LENGTH, - LINK_LENGTH, -}; -use rand::RngCore; - -/// Adds the graph of the given `Keyword` to the given `IndexedValue` to -/// `Keyword`s map. -/// -/// - `keyword` : `Keyword` to upsert as graph -/// - `min_keyword_length` : number of letters to use as graph root -/// - `map` : `IndexedValue` to `Keyword`s map -#[allow(dead_code)] -fn add_keyword_graph( - keyword: &Keyword, - min_keyword_length: usize, - map: &mut HashMap, Keywords>, -) { - for i in min_keyword_length..keyword.len() { - map.entry(IndexedValue::Pointer(Keyword::from(&keyword[..i]))) - .or_default() - .insert(Keyword::from(&keyword[..i - 1])); - } -} - -#[allow(dead_code)] -async fn write_index() -> Result<(), Error> { - const MIN_KEYWORD_LENGTH: usize = 3; - const MAX_NUM_LOCATIONS: usize = 20; - const MAX_FIRST_NAMES: usize = 1000; - - let mut rng = rand::thread_rng(); - - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::random(&mut rng); - - let reader = BufReader::new(File::open("datasets/first_names.txt").unwrap()); - for maybe_line in reader.lines().take(MAX_FIRST_NAMES) { - let line = maybe_line.unwrap(); - let first_name = line.as_str(); - - let n_locations = rng.next_u64() as usize % MAX_NUM_LOCATIONS; - - let mut map = HashMap::with_capacity(n_locations); - for i in 0..n_locations { - map.insert( - IndexedValue::Data(Data::from(format!("{first_name}_{i}").as_bytes())), - Keywords::from_iter([Keyword::from(first_name)]), - ); - } - - add_keyword_graph(&Keyword::from(first_name), MIN_KEYWORD_LENGTH, &mut map); - - findex - .add(&key, &label, IndexedValueToKeywordsMap::from(map)) - .await?; - } - - let mut ser = Serializer::new(); - ser.write(&findex.findex_graph.findex_mm.entry_table.0)?; - ser.write(&findex.findex_graph.findex_mm.chain_table.0)?; - - std::fs::write("datasets/serialized_index", ser.finalize()).unwrap(); - std::fs::write("datasets/key", key.as_bytes()).unwrap(); - std::fs::write("datasets/label", label.as_ref()).unwrap(); - - let keyword = Keyword::from("Abd"); - let res = findex - .search( - &key, - &label, - Keywords::from_iter([keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await?; - - let mut buffer = BufWriter::new(File::create("datasets/test_vector.txt").unwrap()); - for result in res.get(&keyword).unwrap() { - buffer.write_all(result).unwrap(); - buffer.write_all(b"\n").unwrap(); - } - buffer.flush().unwrap(); - - Ok(()) -} - -#[actix_rt::test] -async fn test_non_regression() -> Result<(), Error> { - // Uncomment to generate new test data. - // write_index().await?; - - let mut findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let serialized_index = std::fs::read("datasets/serialized_index").unwrap(); - let mut de = Deserializer::new(&serialized_index); - findex.findex_graph.findex_mm.entry_table.0 = de.read::>()?; - findex.findex_graph.findex_mm.chain_table.0 = de.read::>()?; - - println!( - "Entry Table length: {}", - findex.findex_graph.findex_mm.entry_table.len() - ); - println!( - "Entry Table size: {}", - findex.findex_graph.findex_mm.entry_table.size() - ); - println!( - "Chain Table length: {}", - findex.findex_graph.findex_mm.chain_table.len() - ); - println!( - "Chain Table size: {}", - findex.findex_graph.findex_mm.chain_table.size() - ); - - let key = UserKey::try_from_slice(&std::fs::read("datasets/key").unwrap())?; - let label = Label::from(std::fs::read("datasets/label").unwrap().as_slice()); - - let keyword = Keyword::from("Abd"); - let res = findex - .search( - &key, - &label, - Keywords::from_iter([keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await? - .remove(&keyword) - .unwrap(); - - let mut test_vector = HashSet::new(); - let reader = BufReader::new(File::open("datasets/test_vector.txt").unwrap()); - for maybe_line in reader.lines() { - let line = maybe_line.unwrap(); - test_vector.insert(Data::from(line.as_bytes().to_vec())); - } - - assert_eq!(res, test_vector); - - Ok(()) -} diff --git a/tests/test_in_memory.rs b/tests/test_in_memory.rs deleted file mode 100644 index 1c0ea247..00000000 --- a/tests/test_in_memory.rs +++ /dev/null @@ -1,1174 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - fs::File, - io::{BufRead, BufReader}, - result::Result, - sync::Arc, -}; - -use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng}; -use cosmian_findex::{ - ChainTable, Data, DxEnc, EntryTable, Error, Findex, InMemoryDb, InMemoryDbError, Index, - IndexedValue, IndexedValueToKeywordsMap, Keyword, Keywords, Label, -}; -use futures::executor::block_on; -use rand::Rng; - -const MIN_KEYWORD_LENGTH: usize = 3; - -/// Computes the index graph of the given `Keyword`. -/// -/// - `keyword` : `Keyword` of which to compute the index graph -/// - `min_keyword_length` : number of letters to use as graph root -fn compute_index_graph( - keyword: &Keyword, - min_keyword_length: usize, -) -> HashMap, HashSet> { - if keyword.len() <= min_keyword_length { - return HashMap::new(); - } - let mut res = HashMap::with_capacity(keyword.len() - min_keyword_length); - for i in min_keyword_length..keyword.len() { - let indexing_keyword = Keyword::from(&keyword[..i]); - let indexed_value = IndexedValue::Pointer(Keyword::from(&keyword[..=i])); - res.insert(indexed_value, HashSet::from_iter([indexing_keyword])); - } - res -} - -/// Adds the graph of the given `Keyword` to the given `IndexedValue` to -/// `Keyword`s map. -/// -/// - `keyword` : `Keyword` to upsert as graph -/// - `min_keyword_length` : number of letters to use as graph root -/// - `map` : `IndexedValue` to `Keyword`s map -fn add_keyword_graph( - keyword: &Keyword, - min_keyword_length: usize, - map: &mut HashMap, Keywords>, -) { - let graph = compute_index_graph(keyword, min_keyword_length); - for (key, values) in graph { - let entry = map.entry(key).or_default(); - for value in values { - entry.insert(value); - } - } -} - -/// Check the given keyword has a match in the given search results, and -/// that this match is equal to the given `indexed_value`. -fn check_search_result( - search_results: &HashMap>, - keyword: &Keyword, - location: &Data, -) -> Result<(), String> { - let results = search_results.get(keyword).ok_or_else(|| { - format!( - "keyword '{}' is not present in the given set", - String::from_utf8(keyword.to_vec()).unwrap() - ) - })?; - if results.contains(location) { - Ok(()) - } else { - Err(format!("{location:?} not found for keyword {keyword:?}")) - } -} - -/// Checks the `progress` callback works. -/// -/// The results returned by the callback for a "rob" search should contain -/// either: -/// - a pointer to the "robert" keyword as result for the "rob" keyword -/// - the Location associated to "robert" as result for the "robert" keyword. -/// -/// No further search should be performed after finding the Robert's location. -/// Hence the location associated to the keyword "roberta" should not be -/// returned by `search`. -#[actix_rt::test] -async fn test_progress_callback() -> Result<(), Error> { - let mut indexed_value_to_keywords = HashMap::new(); - - let robert_doe_location = Data::from("Robert Doe's location"); - let roberta_location = Data::from("Roberta's location"); - let rob_location = Data::from("Rob's location"); - let robert_keyword = Keyword::from("robert"); - let rob_keyword = Keyword::from("rob"); - let roberta_keyword = Keyword::from("roberta"); - - // Index locations. - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_doe_location.clone()), - Keywords::from_iter(["robert", "doe"]), - ); - indexed_value_to_keywords.insert( - IndexedValue::Data(rob_location.clone()), - Keywords::from_iter(["rob"]), - ); - indexed_value_to_keywords.insert( - IndexedValue::Data(roberta_location.clone()), - Keywords::from_iter(["robert"]), - ); - - // Index indirections. - indexed_value_to_keywords.insert( - IndexedValue::Pointer(robert_keyword.clone()), - Keywords::from_iter(["rob"]), - ); - indexed_value_to_keywords.insert( - IndexedValue::Pointer(roberta_keyword), - Keywords::from_iter(["robert"]), - ); - - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("First label."); - - findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await?; - - async fn user_interrupt( - local_results: HashMap>>, - ) -> Result { - let mut is_correct = false; - let mut is_last = false; - - for (key, values) in local_results { - if key == Keyword::from("robert") - && values.contains(&IndexedValue::Data(Data::from("Robert Doe's location"))) - { - println!("here"); - is_last = true; - is_correct = true; - } else if key == Keyword::from("rob") - && values.contains(&IndexedValue::Pointer(Keyword::from("robert"))) - { - is_correct = true; - } - } - - Ok(is_last && !is_correct) - } - - let rob_search = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &user_interrupt, - ) - .await?; - - check_search_result(&rob_search, &rob_keyword, &robert_doe_location).unwrap(); - check_search_result(&rob_search, &rob_keyword, &rob_location).unwrap(); - assert!(rob_search - .get(&rob_keyword) - .unwrap() - .contains(&roberta_location)); - - Ok(()) -} - -#[actix_rt::test] -async fn test_deletions() -> Result<(), Error> { - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("First label."); - - // Delete no keyword. - let res = findex - .delete(&key, &label, IndexedValueToKeywordsMap::default()) - .await - .unwrap(); - assert_eq!(res, Keywords::default()); - - // Indexed a location for a keyword. - let res = findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("location")), - Keywords::from_iter([Keyword::from("keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::from_iter([Keyword::from("keyword")])); - - // Indexed another location for this keyword. - let res = findex - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("another location")), - Keywords::from_iter([Keyword::from("keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::default()); - - // Indexed this location for another keyword. - let res = findex - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("location")), - Keywords::from_iter([Keyword::from("another keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::from_iter([Keyword::from("another keyword")])); - - // Indexed this location for this keyword. - let res = findex - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("location")), - Keywords::from_iter([Keyword::from("keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::default()); - - // Nothing is indexed. - let res = findex - .search( - &key, - &label, - Keywords::from_iter([ - Keyword::from("keyword"), - Keyword::from("another keyword"), - Keyword::from("keyword not indexed"), - ]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - - assert_eq!( - res.get(&Keyword::from("keyword")), - Some(HashSet::new()).as_ref() - ); - - assert_eq!( - res.get(&Keyword::from("another keyword")), - Some(HashSet::new()).as_ref() - ); - - // There is no difference between a keyword indexed and not indexed. - assert_eq!( - res.get(&Keyword::from("keyword not indexed")), - Some(HashSet::new()).as_ref() - ); - - assert_eq!(res.get(&Keyword::from("keyword not searched")), None); - - Ok(()) -} - -#[actix_rt::test] -async fn test_double_add() -> Result<(), Error> { - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("First label."); - - // Indexed a first location for the single keyword. - let res = findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("first location")), - Keywords::from_iter([Keyword::from("single keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::from_iter([Keyword::from("single keyword")])); - - // Indexed a second location for the single keyword. - let res = findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from("second location")), - Keywords::from_iter([Keyword::from("single keyword")]), - )]), - ) - .await - .unwrap(); - assert_eq!(res, Keywords::default()); - Ok(()) -} - -#[actix_rt::test] -async fn test_findex() -> Result<(), Error> { - let mut rng = CsRng::from_entropy(); - - let mut removed_items: HashSet = HashSet::new(); - - let robert_keyword = Keyword::from("robert"); - let rob_keyword = Keyword::from("rob"); - let doe_keyword = Keyword::from("doe"); - - let mut indexed_value_to_keywords = HashMap::new(); - - // direct location robert doe - let robert_doe_location = Data::from("robert doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_doe_location.clone()), - Keywords::from_iter(["robert", "doe"]), - ); - - // direct location john doe - let john_doe_location = Data::from("john doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(john_doe_location.clone()), - Keywords::from_iter(["john", "doe"]), - ); - - // direct location for rob... - let rob_location = Data::from("rob DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(rob_location.clone()), - Keywords::from_iter(["rob"]), - ); - // ... and indirection to robert - indexed_value_to_keywords.insert( - IndexedValue::Pointer(robert_keyword.clone()), - Keywords::from_iter(["rob"]), - ); - - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("First label."); - - findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await - .unwrap(); - - // search robert - let robert_search = findex - .search( - &key, - &label, - Keywords::from_iter([robert_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&robert_search, &robert_keyword, &robert_doe_location).unwrap(); - - // cannot find robert with wrong label - let robert_search = findex - .search( - &key, - &Label::random(&mut rng), - Keywords::from_iter([robert_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!(robert_search.get(&robert_keyword), Some(&HashSet::new())); - - // search doe - let doe_search = findex - .search( - &key, - &label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - - // search rob without graph search - let rob_search = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&rob_search, &rob_keyword, &rob_location).unwrap(); - - // search rob with graph search - let rob_search = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&rob_search, &rob_keyword, &robert_doe_location).unwrap(); - check_search_result(&rob_search, &rob_keyword, &rob_location).unwrap(); - - // - // Add Jane Doe to indexes - // - - let jane_keyword = Keyword::from("jane"); - let mut indexed_value_to_keywords = HashMap::new(); - let jane_doe_location = Data::from("jane doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(jane_doe_location.clone()), - Keywords::from_iter(["jane", "doe"]), - ); - findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await - .unwrap(); - - // search jane - let jane_search = findex - .search( - &key, - &label, - Keywords::from_iter([jane_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&jane_search, &jane_keyword, &jane_doe_location).unwrap(); - - // search robert (no change) - let robert_search = findex - .search( - &key, - &label, - Keywords::from_iter([robert_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&robert_search, &robert_keyword, &robert_doe_location).unwrap(); - - // search doe (jane added) - let doe_search = findex - .search( - &key, - &label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!( - doe_search - .get(&doe_keyword) - .map(std::collections::HashSet::len) - .unwrap_or_default(), - 3 - ); - check_search_result(&doe_search, &doe_keyword, &jane_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - - // search rob (no change) - let rob_search = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&rob_search, &rob_keyword, &rob_location).unwrap(); - - let mut old_key; - let mut old_label; - let mut new_label = label; - let mut new_key = key; - - // If nothing is removed, a lot of small compact should not affect the - // search results - for i in 1..=100 { - println!("Compacting {i}/100"); - old_key = new_key; - old_label = new_label; - new_label = Label::random(&mut rng); - new_key = findex.keygen(); - findex - .compact( - &old_key, - &new_key, - &old_label, - &new_label, - 1f64 / f64::from(i), - &|indexed_data| async { - Ok(indexed_data - .into_iter() - .filter(|data| !removed_items.contains(data)) - .collect()) - }, - ) - .await - .unwrap(); - - let doe_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &jane_doe_location).unwrap(); - } - - // Remove the location "Jane Doe" from the DB. The next compact operation should - // remove it from the index. - removed_items.insert(jane_doe_location); - - old_key = new_key; - new_key = findex.keygen(); - old_label = new_label; - new_label = Label::random(&mut rng); - findex - .compact( - &old_key, - &new_key, - &old_label, - &new_label, - 1f64, - &|indexed_data| async { - Ok(indexed_data - .into_iter() - .filter(|data| !removed_items.contains(data)) - .collect()) - }, - ) - .await - .unwrap(); - - // search jane - let jane_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([jane_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - - // Jane is not indexed anymore. - assert_eq!(jane_search.get(&jane_keyword), Some(&HashSet::new())); - - // search doe (jane removed) - let doe_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - - // Cannot search doe with the old label - let doe_search = findex - .search( - &new_key, - &old_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!(doe_search.get(&doe_keyword), Some(&HashSet::new())); - - for i in 1..=100 { - println!("Compacting {i}/100"); - old_key = new_key; - new_key = findex.keygen(); - old_label = new_label; - new_label = Label::random(&mut rng); - findex - .compact( - &old_key, - &new_key, - &old_label, - &new_label, - 1f64 / f64::from(i), - &|indexed_data| async { - Ok(indexed_data - .into_iter() - .filter(|data| !removed_items.contains(data)) - .collect()) - }, - ) - .await - .unwrap(); - } - - // search doe (jane removed) - let doe_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - - // Cannot search doe with the old label - let doe_search = findex - .search( - &new_key, - &old_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!(doe_search.get(&doe_keyword), Some(&HashSet::new())); - - for i in 1..100 { - old_label = new_label; - new_label = Label::random(&mut rng); - old_key = new_key; - new_key = findex.keygen(); - findex - .compact( - &old_key, - &new_key, - &old_label, - &new_label, - 1f64 / f64::from(i), - &|indexed_data| async { - Ok(indexed_data - .into_iter() - .filter(|data| !removed_items.contains(data)) - .collect()) - }, - ) - .await - .unwrap(); - - // search doe (jane removed) - let doe_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - check_search_result(&doe_search, &doe_keyword, &john_doe_location).unwrap(); - } - - // Try deleting John Doe from the `doe_keyword`. - let mut deletions = HashMap::new(); - deletions.insert( - IndexedValue::Data(john_doe_location.clone()), - Keywords::from_iter([doe_keyword.clone()]), - ); - findex - .delete( - &new_key, - &new_label, - IndexedValueToKeywordsMap::from(deletions), - ) - .await - .unwrap(); - - // Assert John Doe cannot be found by searching for Doe. - let doe_search = findex - .search( - &new_key, - &new_label, - Keywords::from_iter([doe_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - check_search_result(&doe_search, &doe_keyword, &robert_doe_location).unwrap(); - let doe_search = doe_search.get(&doe_keyword).unwrap(); - assert!(!doe_search.contains(&john_doe_location)); - Ok(()) -} - -#[actix_rt::test] -async fn test_first_names() -> Result<(), Error> { - const NUM_LOCATIONS: usize = 5; - // change this to usize::MAX to run a full test - const MAX_FIRST_NAMES: usize = 1000; - - let mut rng = rand::thread_rng(); - - let graph_findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let naive_findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = graph_findex.keygen(); - - // Keywords that will be searched later to run tests - let mut searches: HashSet = HashSet::new(); - let mut first_names_number = 0; - let mut first_names_total_len = 0; - - let label = Label::from("label"); - - let file = File::open("datasets/first_names.txt").unwrap(); - let reader = BufReader::new(file); - println!("Indexing..."); - for maybe_line in reader.lines() { - let line = maybe_line.unwrap(); - let first_name = line.as_str(); - - first_names_number += 1; - first_names_total_len += first_name.len(); - - // pick one keyword out of about 5 to be searched later - let die = rng.gen_range(1_i32..=5); - if die == 1 { - // select between 3 to keyword length characters - let die = if first_name.len() <= 3 { - first_name.len() - } else { - rng.gen_range(3..=first_name.len()) - }; - let searched_keyword: String = first_name.chars().take(die).collect(); - searches.insert(searched_keyword); - } - - // start with graph - let mut map = HashMap::with_capacity(5); - for i in 0..NUM_LOCATIONS { - map.insert( - IndexedValue::Data(Data::from(format!("{first_name}_{i}").as_bytes())), - Keywords::from_iter([Keyword::from("france"), Keyword::from(first_name)]), - ); - add_keyword_graph(&Keyword::from(first_name), MIN_KEYWORD_LENGTH, &mut map); - } - - graph_findex - .add(&key, &label, IndexedValueToKeywordsMap::from(map)) - .await - .unwrap(); - - // naive Findex - let mut keywords = HashSet::::new(); - if first_name.len() < MIN_KEYWORD_LENGTH { - // index as such - keywords.insert(Keyword::from(first_name)); - } else { - // index all slices starting from 3 - let mut current: Vec = Vec::new(); - for (i, c) in first_name.chars().enumerate() { - current.push(c); - if i + 1 >= MIN_KEYWORD_LENGTH { - let current_keyword: String = current.iter().collect(); - keywords.insert(Keyword::from(current_keyword.as_str())); - } - } - } - let mut map_naive = HashMap::new(); - for i in 0..NUM_LOCATIONS { - let iv = IndexedValue::Data(Data::from(format!("{first_name}_{i}").as_str())); - map_naive.insert(iv, Keywords::from(keywords.clone())); - } - naive_findex - .add(&key, &label, IndexedValueToKeywordsMap::from(map_naive)) - .await - .unwrap(); - - if first_names_number % 1000 == 0 { - println!(" ...{first_names_number}"); - } - if first_names_number >= MAX_FIRST_NAMES { - break; - } - } - println!(" ...done"); - - println!( - "Indexed {} keywords with an average length of {} chars and an average of {} locations", - first_names_number, - first_names_total_len / first_names_number, - NUM_LOCATIONS - ); - println!("Built a list of {} search keywords", searches.len()); - println!( - "Graphs: table sizes: entry -> {} records, {} kbytes, chain -> {} records, {} kbytes", - graph_findex.findex_graph.findex_mm.entry_table.len(), - graph_findex.findex_graph.findex_mm.entry_table.size() / 1024, - graph_findex.findex_graph.findex_mm.chain_table.len(), - graph_findex.findex_graph.findex_mm.chain_table.size() / 1024 - ); - println!( - "Naive: table sizes: entry -> {} records, {} kbytes, chain -> {} records, {} kbytes", - naive_findex.findex_graph.findex_mm.entry_table.len(), - naive_findex.findex_graph.findex_mm.entry_table.size() / 1024, - naive_findex.findex_graph.findex_mm.chain_table.len(), - naive_findex.findex_graph.findex_mm.chain_table.size() / 1024 - ); - - let mut total_results = 0_usize; - let num_searches = searches.len(); - for s in searches { - let keywords = Keywords::from_iter([Keyword::from(s.as_str())]); - let graph_results = graph_findex - .search(&key, &label, keywords.clone(), &|_| async { Ok(false) }) - .await - .unwrap(); - assert!( - !graph_results.is_empty(), - "No graph results for keyword: {s}! This should not happen" - ); - total_results += graph_results.len(); - // naive search - let naive_results = naive_findex - .search(&key, &label, keywords, &|_| async { Ok(false) }) - .await - .unwrap(); - assert_eq!( - graph_results.len(), - naive_results.len(), - "failed on keyword {s}:\n{graph_results:?}\n{naive_results:?}", - ); - } - println!( - "Graphs: average per search: {} results", - total_results / num_searches, - ); - - Ok(()) -} - -#[actix_rt::test] -async fn test_graph_compacting() { - let mut rng = CsRng::from_entropy(); - let mut indexed_value_to_keywords = HashMap::new(); - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let mut key = findex.keygen(); - - let rob_keyword = Keyword::from(b"rob".to_vec()); - let doe_keyword = Keyword::from(b"doe".to_vec()); - let john_keyword = Keyword::from(b"john".to_vec()); - let robert_keyword = Keyword::from(b"robert".to_vec()); - let john_doe_location = Data::from("john doe DB location"); - let robert_doe_location = Data::from("robert doe DB location"); - - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_doe_location.clone()), - Keywords::from_iter([robert_keyword.clone(), doe_keyword.clone()]), - ); - indexed_value_to_keywords.insert( - IndexedValue::Data(john_doe_location.clone()), - Keywords::from_iter([john_keyword.clone(), doe_keyword]), - ); - add_keyword_graph( - &john_keyword, - MIN_KEYWORD_LENGTH, - &mut indexed_value_to_keywords, - ); - add_keyword_graph( - &robert_keyword, - MIN_KEYWORD_LENGTH, - &mut indexed_value_to_keywords, - ); - - // Graph upsert - let mut label = Label::random(&mut rng); - findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await - .unwrap(); - - // Search for "rob" - let res = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!(res.len(), 1); - check_search_result(&res, &rob_keyword, &robert_doe_location).unwrap(); - - println!( - "Length of the Entry Table: {}", - findex.findex_graph.findex_mm.entry_table.len() - ); - println!( - "Length of the Chain Table: {}", - findex.findex_graph.findex_mm.chain_table.len() - ); - - // Compact then search - for i in 1..100 { - let old_label = label; - label = Label::random(&mut rng); - let new_key = findex.keygen(); - findex - .compact( - &key, - &new_key, - &old_label, - &label, - 1f64 / f64::from(i), - &|indexed_data| async { Ok(indexed_data) }, - ) - .await - .unwrap(); - key = new_key; - - println!( - "Length of the Entry Table: {}", - findex.findex_graph.findex_mm.entry_table.len() - ); - println!( - "Length of the Chain Table: {}", - findex.findex_graph.findex_mm.chain_table.len() - ); - - // Search for "rob" - let res = findex - .search( - &key, - &label, - Keywords::from_iter([rob_keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await - .unwrap(); - assert_eq!(res.len(), 1); - check_search_result(&res, &rob_keyword, &robert_doe_location).unwrap(); - } -} - -#[actix_rt::test] -async fn test_keyword_presence() -> Result<(), Error> { - let mut indexed_value_to_keywords = HashMap::new(); - - // direct location robert doe - let robert_doe_location = Data::from("robert doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_doe_location.clone()), - Keywords::from_iter(["robert", "doe"]), - ); - - // direct location john doe - let john_doe_location = Data::from("john doe DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(john_doe_location.clone()), - Keywords::from_iter(["john", "doe"]), - ); - - let findex = Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - ); - - let key = findex.keygen(); - let label = Label::from("First label."); - - let new_keywords = findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await?; - - // the 3 keywords should not be present in the database - assert_eq!(new_keywords.len(), 3); - assert!(new_keywords.contains(&Keyword::from("robert"))); - assert!(new_keywords.contains(&Keyword::from("doe"))); - assert!(new_keywords.contains(&Keyword::from("john"))); - - // Now insert a Robert Smith - let mut indexed_value_to_keywords = HashMap::new(); - let robert_smith_location = Data::from("robert smith DB location"); - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_smith_location), - Keywords::from_iter(["robert", "smith"]), - ); - let new_keywords = findex - .add( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords), - ) - .await?; - // robert should be present, but not smith - assert_eq!(new_keywords.len(), 1); - assert!(!new_keywords.contains(&Keyword::from("robert"))); - assert!(new_keywords.contains(&Keyword::from("smith"))); - - // Delete Robert Smith and the junior keyword - let robert_smith_location = Data::from("robert smith DB location"); - let mut indexed_value_to_keywords = HashMap::new(); - indexed_value_to_keywords.insert( - IndexedValue::Data(robert_smith_location.clone()), - Keywords::from_iter(["robert", "smith", "junior"]), - ); - let new_keywords = findex - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords.clone()), - ) - .await?; - // robert and smith should be present, but not junior - assert_eq!(new_keywords.len(), 1); - assert!(!new_keywords.contains(&Keyword::from("robert"))); - assert!(!new_keywords.contains(&Keyword::from("smith"))); - assert!(new_keywords.contains(&Keyword::from("junior"))); - - // however, the first delete create an entry for "junior, - // therefore deleting again will find it - let new_keywords = findex - .delete( - &key, - &label, - IndexedValueToKeywordsMap::from(indexed_value_to_keywords.clone()), - ) - .await?; - // all should be present - assert_eq!(new_keywords.len(), 0); - - Ok(()) -} - -#[actix_rt::test] -async fn test_concurrency() -> Result<(), Error> { - let findex = Arc::new(Findex::new( - EntryTable::setup(InMemoryDb::default()), - ChainTable::setup(InMemoryDb::default()), - )); - let key = Arc::new(findex.keygen()); - let label = Arc::new(Label::from("First label.")); - let keyword = Keyword::from("unique keyword"); - - let handles = (0..100) - .map(|id: usize| { - let findex = findex.clone(); - let key = key.clone(); - let label = label.clone(); - let keyword = keyword.clone(); - - std::thread::spawn(move || { - let res = block_on(findex.add( - &key, - &label, - IndexedValueToKeywordsMap::from([( - IndexedValue::Data(Data::from(id.to_be_bytes().as_slice())), - Keywords::from_iter([keyword]), - )]), - )); - (id, res) - }) - }) - .collect::>(); - - let mut new_keywords = HashSet::new(); - for h in handles { - let (id, res) = h.join().unwrap(); - let res = res.unwrap(); - for keyword in res { - assert!( - new_keywords.insert(keyword), - "{id}: same keyword cannot be returned twice." - ); - } - } - - assert_eq!(new_keywords, HashSet::from_iter([keyword.clone()])); - - let res = findex - .search( - &key, - &label, - Keywords::from_iter([keyword.clone()]), - &|_| async { Ok(false) }, - ) - .await?; - - assert_eq!( - res.get(&keyword), - Some( - (0..100) - .map(|id: usize| Data::from(id.to_be_bytes().as_slice())) - .collect() - ) - .as_ref() - ); - - Ok(()) -} From 87f820df498c80e7c6982e87e8c5169c320612b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 18 Jun 2024 14:52:18 +0200 Subject: [PATCH 02/42] add OVec --- src/address.rs | 61 ++++++++++++- src/error.rs | 17 ++-- src/kv.rs | 19 ++-- src/lib.rs | 1 + src/obf.rs | 90 ++++++++++++------- src/ovec.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++++++ src/stm.rs | 4 +- 7 files changed, 374 insertions(+), 49 deletions(-) create mode 100644 src/ovec.rs diff --git a/src/address.rs b/src/address.rs index 4c579da8..c1fd1261 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,4 +1,4 @@ -use std::ops::{Deref, DerefMut}; +use std::ops::{Add, Deref, DerefMut}; use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; @@ -26,3 +26,62 @@ impl Address { res } } + +impl Add for Address { + type Output = Address; + + /// Highly inefficient implementation of a add modulo 2^8^LENGTH in little endian. + fn add(mut self, mut adder: u64) -> Self::Output { + let mut carry = 0; + let mut pos = 0; + while 0 < carry || adder != 0 { + // add bytes + let lhs = &mut self[pos % LENGTH]; + let rhs = adder % 256; + + let res = *lhs as i32 + rhs as i32 + carry; + + println!( + "(pos, lhs, rhs, carry, res) = {:?}", + (pos, *lhs, (rhs % 256), carry, res) + ); + + // update states + *lhs = (res % 256) as u8; + carry = res >> 8; + adder >>= 8; + pos += 1; + + if (pos % LENGTH) == 0 { + println!("circling"); + carry -= 1; + } + } + self + } +} + +#[cfg(test)] +mod tests { + use crate::address::Address; + + #[test] + fn test_add() { + // Test with one byte overflow. + assert_eq!(Address([0]) + 0, Address([0])); + assert_eq!(Address([0]) + 1, Address([1])); + assert_eq!(Address([0]) + 256, Address([0])); // 256 is the neutral element + + // Test with two bytes overflow. + assert_eq!(Address([0, 0]) + 1, Address([1, 0])); + assert_eq!(Address([0, 0]) + 256, Address([0, 1])); // 256 is a shift + + // random add + assert_eq!(4325 >> 8, 16); + assert_eq!(4325 % 256, 229); + assert_eq!(229 + 100 - 256, 73); // there will be a carry + assert_eq!(Address([100, 10]) + 4325, Address([73, 27])); + + assert_eq!(Address([0, 0]) + (1 << 16), Address([0, 0])); // 2^16 is the neutral element + } +} diff --git a/src/error.rs b/src/error.rs index 4626835a..70d97c87 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,15 +1,17 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use cosmian_crypto_core::CryptoCoreError; #[derive(Debug)] -pub enum Error { +pub enum Error { Parsing(String), Encryption(CryptoCoreError), Memory(MemoryError), + Conversion(String), + MissingValue(Address), } -impl Display for Error { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Parsing(e) => write!(f, "{}", e), @@ -18,9 +20,14 @@ impl Display for Error { } } -impl std::error::Error for Error {} +impl std::error::Error + for Error +{ +} -impl From for Error { +impl From + for Error +{ fn from(e: MemoryError) -> Self { Self::Memory(e) } diff --git a/src/kv.rs b/src/kv.rs index a0393acd..89bb3f06 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -33,12 +33,11 @@ impl Stm for KvStore) -> Result)>, Self::Error> { + fn batch_read(&self, a: Vec
) -> Result>, Self::Error> { let store = &mut *self.0.lock().expect("poisoned lock"); Ok(a.into_iter() .map(|k| { let v = store.get(&k).cloned(); - //println!("get\t{:?} -> {:?}", k, v); (k, v) }) .collect()) @@ -51,20 +50,20 @@ impl Stm for KvStore Result, Self::Error> { let store = &mut *self.0.lock().expect("poisoned lock"); let (a, old) = guard; - let cur = store.get(&a); - if old.as_ref() == cur { + let cur = store.get(&a).cloned(); + if old == cur { for (k, v) in bindings { - println!("set\t{:?}", k); store.insert(k, v); } } - let new = store.get(&a).cloned(); - Ok(new) + Ok(cur) } } #[cfg(test)] mod tests { + use std::collections::HashMap; + use crate::stm::Stm; use super::KvStore; @@ -79,7 +78,7 @@ mod tests { assert_eq!( kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)]) .unwrap(), - Some(2) + None ); assert_eq!( kv.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)]) @@ -89,10 +88,10 @@ mod tests { assert_eq!( kv.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)]) .unwrap(), - Some(4) + Some(2) ); assert_eq!( - vec![(1, Some(1)), (2, Some(1)), (3, Some(3)), (4, Some(3))], + HashMap::from_iter([(1, Some(1)), (2, Some(1)), (3, Some(3)), (4, Some(3))]), kv.batch_read(vec![1, 2, 3, 4]).unwrap(), ) } diff --git a/src/lib.rs b/src/lib.rs index 7fa60e33..6c49bec8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod address; mod error; mod kv; mod obf; +mod ovec; mod stm; pub use stm::Stm; diff --git a/src/obf.rs b/src/obf.rs index a8e30a73..764cb91c 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -1,13 +1,13 @@ use std::{ cmp::Ordering, - collections::HashMap, + collections::{HashMap, HashSet}, ops::DerefMut, sync::{Mutex, MutexGuard}, }; use crate::{address::Address, error::Error, Stm}; use aes::{ - cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, + cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit}, Aes256, }; use cosmian_crypto_core::{ @@ -43,6 +43,15 @@ impl, Word = Vec>> Obfuscation self.rng.lock().expect("poisoned lock") } + /// Retains values cached for the given keys only. + pub fn retain_cached_keys(&self, keys: &HashSet<(Address, Vec)>) { + self.cache + .lock() + .expect("poisoned mutex") + .deref_mut() + .retain(|k, _| keys.contains(k)); + } + /// Shuffles the given list of values. fn shuffle(&self, mut v: Vec) -> Vec { v.sort_by(|_, _| { @@ -55,38 +64,53 @@ impl, Word = Vec>> Obfuscation v } + /// Get the encrypted value from the cache, compute it upon cache miss. pub fn encrypt( &self, ptx: &[u8], tok: &Address, - ) -> Result, Error> { - let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); - let ctx = Aes256Gcm::new(&self.encryption_key) - .encrypt(&nonce, ptx, Some(tok)) - .map_err(Error::Encryption)?; - let ctx = [nonce.as_bytes(), &ctx].concat(); - Ok(ctx) + ) -> Result, Error, Memory::Error>> { + let k = (tok.clone(), ptx.to_vec()); + if let Some(ctx) = self.find(&k) { + Ok(ctx) + } else { + let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); + let ctx = Aes256Gcm::new(&self.encryption_key) + .encrypt(&nonce, ptx, Some(tok)) + .map_err(Error::Encryption)?; + let ctx = [nonce.as_bytes(), &ctx].concat(); + self.bind(k, ctx.clone()); + Ok(ctx) + } } + /// Decrypt the given value, and cache the ciphertext. pub fn decrypt( &self, - ctx: &[u8], - tok: &Address, - ) -> Result, Error> { + ctx: Vec, + tok: Address, + ) -> Result, Error, Memory::Error>> { if ctx.len() < Aes256Gcm::NONCE_LENGTH { return Err(Error::Parsing("ciphertext too small".to_string())); } let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) .map_err(|e| Error::Parsing(e.to_string()))?; let ptx = Aes256Gcm::new(&self.encryption_key) - .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(tok)) + .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(&tok)) .map_err(Error::Encryption)?; + self.bind((tok, ptx.clone()), ctx); Ok(ptx) } + pub fn reorder(&self, mut a: Address) -> Address { + Aes256::new(GenericArray::from_slice(&self.permutation_key)) + .decrypt_block(GenericArray::from_mut_slice(&mut a)); + a + } + pub fn permute(&self, mut a: Address) -> Address { - let e = Aes256::new(GenericArray::from_slice(&self.permutation_key)); - e.encrypt_block(GenericArray::from_mut_slice(&mut a)); + Aes256::new(GenericArray::from_slice(&self.permutation_key)) + .encrypt_block(GenericArray::from_mut_slice(&mut a)); a } @@ -117,23 +141,26 @@ impl< type Word = Memory::Word; - type Error = Error; + type Error = Error; fn batch_read( &self, addresses: Vec, - ) -> Result)>, Self::Error> { - let addresses = self.shuffle(addresses); - let tokens = addresses.iter().cloned().map(|a| self.permute(a)).collect(); + ) -> Result>, Self::Error> { + let tokens = self + .shuffle(addresses) + .into_iter() + .map(|a| self.permute(a)) + .collect(); + let bindings = self.stm.batch_read(tokens)?; - addresses + + bindings .into_iter() - .zip(bindings.iter()) - .map(|(a, (tok, ctx))| { - ctx.as_deref() - .map(|ctx| self.decrypt(ctx, tok)) + .map(|(tok, ctx)| { + ctx.map(|ctx| self.decrypt(ctx, tok.clone())) .transpose() - .map(|maybe_ctx| (a, maybe_ctx)) + .map(|maybe_ctx| (self.reorder(tok), maybe_ctx)) }) .collect() } @@ -144,8 +171,8 @@ impl< bindings: Vec<(Self::Address, Self::Word)>, ) -> Result, Self::Error> { let (a, v) = guard; - let tok = self.permute(a.clone()); - let old = v.and_then(|v| self.find(&(a.clone(), v))); + let tok = self.permute(a); + let old = v.and_then(|v| self.find(&(tok.clone(), v))); let bindings = bindings .into_iter() @@ -158,8 +185,7 @@ impl< let ctx = self.stm.guarded_write((tok.clone(), old), bindings)?; if let Some(ctx) = ctx { - let ptx = self.decrypt(&ctx, &tok)?; - self.bind((a, ptx.clone()), ctx); + let ptx = self.decrypt(ctx.clone(), tok)?; Ok(Some(ptx)) } else { Ok(None) @@ -189,7 +215,7 @@ mod tests { let tok = Address::::random(&mut rng); let ptx = vec![1]; let ctx = obf.encrypt(&ptx, &tok).unwrap(); - let res = obf.decrypt(&ctx, &tok).unwrap(); + let res = obf.decrypt(ctx, tok).unwrap(); assert_eq!(ptx.len(), res.len()); assert_eq!(ptx, res); } @@ -221,7 +247,7 @@ mod tests { ] ) .unwrap(), - Some(vec![2]) + None ); assert_eq!( @@ -247,7 +273,7 @@ mod tests { ] ) .unwrap(), - Some(vec![4]) + Some(vec![2]) ); assert_eq!( diff --git a/src/ovec.rs b/src/ovec.rs new file mode 100644 index 00000000..9edc80ce --- /dev/null +++ b/src/ovec.rs @@ -0,0 +1,231 @@ +use std::{fmt::Debug, hash::Hash, ops::Add}; + +use crate::{error::Error, Stm}; + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +struct Header { + pub(crate) start: u64, + pub(crate) stop: u64, +} + +impl From<&Header> for Vec { + fn from(header: &Header) -> Self { + let stop = header.stop.to_be_bytes(); + let start = header.start.to_be_bytes(); + [start, stop].concat() + } +} + +impl From
for Vec { + fn from(value: Header) -> Self { + Self::from(&value) + } +} + +impl TryFrom<&[u8]> for Header { + type Error = String; + + fn try_from(value: &[u8]) -> Result { + if value.len() < 16 { + return Err(format!( + "value to short to be converted to header: need {}, got {}", + 16, + value.len() + )); + } + let start = <[u8; 8]>::try_from(&value[..8]).expect("length is correct"); + let stop = <[u8; 8]>::try_from(&value[8..]).expect("length is correct"); + Ok(Self { + start: u64::from_be_bytes(start), + stop: u64::from_be_bytes(stop), + }) + } +} + +/// A client-side implementation of a vector. +/// +/// ```txt +/// +------------distant-memory-----------+ +/// | | +/// a --|--> | header (h) | v_1 | ... | v_n | | +/// | | +/// +------------distant-memory-----------+ +/// ``` +#[derive(Debug, Clone)] +pub struct OVec<'a, Memory: Stm>> { + a: Memory::Address, + h: Option
, + m: &'a Memory, +} + +impl< + 'a, + Address: Hash + Eq + Debug + Clone + Add, + Memory: Stm
>, + > OVec<'a, Memory> +{ + pub fn push(&mut self, values: Vec>) -> Result<(), Error> { + let try_push = + |old: Option<&Header>| -> Result<(Option
, Header), Error> { + // Generates a new header which counter is incremented. + let mut new = old.cloned().unwrap_or_default(); + new.stop += values.len() as u64; + + // Binds the correct addresses to the values. + let mut bindings = values + .iter() + .cloned() + .enumerate() + .map(|(i, v)| (self.a.clone() + new.start + 1 + i as u64, v)) + .collect::>(); + bindings.push((self.a.clone(), (&new).into())); + + // Attempts committing the new bindings using the old header as guard. + let cur = self + .m + .guarded_write((self.a.clone(), old.map(>::from)), bindings)? + .map(|v| Header::try_from(v.as_slice())) + .transpose() + .map_err(Error::Conversion)?; + + Ok((cur, new)) + }; + + loop { + let old = self.h.as_ref(); + let (cur, new) = try_push(old)?; + if cur.as_ref() == old { + self.h = Some(new); + return Ok(()); + } else { + self.h = cur; + } + } + } + + pub fn read(&self) -> Result>, Error> { + // Read a first batch of addresses: + // - the header address; + // - the value addresses derived from the known header. + let header = self.h.clone().unwrap_or_default(); + let addresses = (header.start + 1..header.stop + 1) + .chain(0..=0) + .map(|i| self.a.clone() + i) + .collect(); + + let mut res = self.m.batch_read(addresses)?; + + let current_header = res + .get(&self.a) + .ok_or_else(|| Error::MissingValue(self.a.clone()))? + .as_ref() + .map(|v| Header::try_from(v.as_slice())) + .transpose() + .map_err(Error::Conversion)? + .unwrap_or_default(); + + let res = (current_header.start..current_header.stop) + .map(|i| self.a.clone() + 1 + i) + .map(|a| { + let v = res.remove(&a).flatten(); + (a, v) + }) + .collect::>(); + + // Read missing values if any. + let mut missing_res = self.m.batch_read( + res.iter() + .filter_map(|(a, v)| if v.is_none() { Some(a) } else { None }) + .cloned() + .collect(), + )?; + + res.into_iter() + .map(|(a, maybe_v)| { + maybe_v + .or_else(|| missing_res.remove(&a).flatten()) + .ok_or_else(|| Error::MissingValue(a.clone())) + }) + .collect() + } +} + +//#[cfg(test)] +//mod tests { +///// Ensures a transaction can express an vector push operation: +///// - the counter is correctly incremented and all values are written; +///// - using the wrong value in the guard fails the operation and returns the current value. +//fn test_vector_push() { +//let mut rng = CsRng::from_entropy(); +//let seed = Secret::random(&mut rng); +//let kv = KvStore::, Vec>::new(); +//let obf = ObfuscationLayer::new(seed, rng.clone(), kv); +//let ovec = OVec::new(seed, rng.clone(), obf); + +//let header_addr = Address::::random(&mut rng); + +//let val_addr_1 = Address::::random(&mut rng); +//let val_addr_2 = Address::::random(&mut rng); +//let val_addr_3 = Address::::random(&mut rng); +//let val_addr_4 = Address::::random(&mut rng); + +//assert_eq!( +//obf.guarded_write( +//(header_addr.clone(), None), +//vec![ +//(header_addr.clone(), vec![2]), +//(val_addr_1.clone(), vec![1]), +//(val_addr_2.clone(), vec![1]) +//] +//) +//.unwrap(), +//None +//); + +//assert_eq!( +//obf.guarded_write( +//(header_addr.clone(), None), +//vec![ +//(header_addr.clone(), vec![2]), +//(val_addr_1.clone(), vec![3]), +//(val_addr_2.clone(), vec![3]) +//] +//) +//.unwrap(), +//Some(vec![2]) +//); + +//assert_eq!( +//obf.guarded_write( +//(header_addr.clone(), Some(vec![2])), +//vec![ +//(header_addr.clone(), vec![4]), +//(val_addr_3.clone(), vec![2]), +//(val_addr_4.clone(), vec![2]) +//] +//) +//.unwrap(), +//Some(vec![2]) +//); + +//assert_eq!( +//HashSet::<(Address, Option>)>::from_iter([ +//(header_addr.clone(), Some(vec![4])), +//(val_addr_1.clone(), Some(vec![1])), +//(val_addr_2.clone(), Some(vec![1])), +//(val_addr_3.clone(), Some(vec![2])), +//(val_addr_4.clone(), Some(vec![2])) +//]), +//HashSet::from_iter( +//obf.batch_read(vec![ +//header_addr, +//val_addr_1, +//val_addr_2, +//val_addr_3, +//val_addr_4 +//]) +//.unwrap() +//), +//) +//} +//} diff --git a/src/stm.rs b/src/stm.rs index 621a7a06..184db396 100644 --- a/src/stm.rs +++ b/src/stm.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + pub trait Stm { /// Address space. type Address; @@ -12,7 +14,7 @@ pub trait Stm { fn batch_read( &self, a: Vec, - ) -> Result)>, Self::Error>; + ) -> Result>, Self::Error>; /// Adds the given memory bindings if the guard binding is stored. /// Returns the value of the guarded word after the writes. From a21435686c0834f944bc8bc008ec0733f4081894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 18 Jun 2024 17:17:38 +0200 Subject: [PATCH 03/42] add index --- src/address.rs | 12 ++-- src/findex.rs | 115 ++++++++++++++++++++++++++++++++++++ src/kv.rs | 11 +--- src/lib.rs | 33 ++++++++++- src/obf.rs | 38 ++++++------ src/ovec.rs | 154 +++++++++++++++++++++---------------------------- src/value.rs | 48 +++++++++++++++ 7 files changed, 289 insertions(+), 122 deletions(-) create mode 100644 src/findex.rs create mode 100644 src/value.rs diff --git a/src/address.rs b/src/address.rs index c1fd1261..c1f93d0a 100644 --- a/src/address.rs +++ b/src/address.rs @@ -19,6 +19,12 @@ impl DerefMut for Address { } } +impl Default for Address { + fn default() -> Self { + Self([0; LENGTH]) + } +} + impl Address { pub fn random(rng: &mut impl CryptoRngCore) -> Self { let mut res = Self([0; LENGTH]); @@ -38,14 +44,8 @@ impl Add for Address { // add bytes let lhs = &mut self[pos % LENGTH]; let rhs = adder % 256; - let res = *lhs as i32 + rhs as i32 + carry; - println!( - "(pos, lhs, rhs, carry, res) = {:?}", - (pos, *lhs, (rhs % 256), carry, res) - ); - // update states *lhs = (res % 256) as u8; carry = res >> 8; diff --git a/src/findex.rs b/src/findex.rs new file mode 100644 index 00000000..7f75f3fb --- /dev/null +++ b/src/findex.rs @@ -0,0 +1,115 @@ +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, + sync::{Arc, Mutex}, +}; + +use cosmian_crypto_core::{kdf128, CsRng, Secret}; + +use crate::{ + error::Error, obf::EncryptionLayer, ovec::OVec, Address, Index, Stm, ADDRESS_LENGTH, KEY_LENGTH, +}; + +pub struct Findex<'a, Memory: 'a + Stm
, Word = Vec>> { + el: EncryptionLayer, + vectors: Mutex, OVec<'a, EncryptionLayer>>>, +} + +impl<'a, Memory: 'a + Stm
, Word = Vec>> Findex<'a, Memory> { + pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { + Self { + el: EncryptionLayer::new(seed, rng, stm), + vectors: Mutex::new(HashMap::new()), + } + } +} + +impl< + 'a, + Keyword: Hash + PartialEq + Eq + AsRef<[u8]>, + Value: Hash + PartialEq + Eq + From>, + Memory: 'a + Stm
, Word = Vec>, + > Index<'a, Keyword, Value> for Findex<'a, Memory> +where + for<'z> Vec: From<&'z Value>, +{ + type Error = Error, as Stm>::Error>; + + fn search( + &'a self, + keywords: impl Iterator, + ) -> Result>, Self::Error> { + keywords + .map(|kw| { + let mut a = Address::::default(); + kdf128!(&mut a, kw.as_ref()); + let mut vectors = self.vectors.lock().expect("poisoned mutex"); + let vector = vectors + .entry(a.clone()) + .or_insert_with(|| OVec::<'a, EncryptionLayer>::new(a, &self.el)); + let links = vector.read()?; + Ok(( + kw, + links.into_iter().map(Value::from).collect::>(), + )) + }) + .collect() + } + + fn insert( + &'a self, + bindings: impl Iterator)>, + ) -> Result<(), Self::Error> { + bindings + .map(|(kw, vals)| { + let mut a = Address::::default(); + kdf128!(&mut a, kw.as_ref()); + let mut vectors = self.vectors.lock().expect("poisoned mutex"); + let vector = vectors + .entry(a.clone()) + .or_insert_with(|| OVec::<'a, EncryptionLayer>::new(a, &self.el)); + vector.push(vals.iter().map(>::from).collect::>()) + }) + .collect::, _>>()?; + Ok(()) + } + + fn delete( + _bindings: impl Iterator)>, + ) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, Mutex}, + }; + + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + + use crate::{address::Address, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH}; + + #[test] + fn test_insert_search() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::default(); + let findex = Findex::new(seed, Arc::new(Mutex::new(rng)), kv); + let bindings = HashMap::<&str, HashSet>::from_iter([ + ( + "cat", + HashSet::from_iter([Value::from(1), Value::from(3), Value::from(5)]), + ), + ( + "dog", + HashSet::from_iter([Value::from(0), Value::from(2), Value::from(4)]), + ), + ]); + findex.insert(bindings.clone().into_iter()).unwrap(); + let res = findex.search(bindings.keys().cloned()).unwrap(); + assert_eq!(bindings, res); + } +} diff --git a/src/kv.rs b/src/kv.rs index 89bb3f06..811378ae 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -18,14 +18,9 @@ impl Display for MemoryError { impl std::error::Error for MemoryError {} +#[derive(Clone, Debug, Default)] pub struct KvStore(Arc>>); -impl KvStore { - pub fn new() -> Self { - Self(Arc::new(Mutex::new(HashMap::new()))) - } -} - impl Stm for KvStore { type Address = Address; @@ -64,7 +59,7 @@ impl Stm for KvStore::new(); + let kv = KvStore::::default(); assert_eq!( kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)]) diff --git a/src/lib.rs b/src/lib.rs index 6c49bec8..437efdcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,42 @@ -#![allow(dead_code)] #![allow(clippy::type_complexity)] mod address; mod error; +mod findex; mod kv; mod obf; mod ovec; mod stm; +mod value; +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, +}; + +pub use address::Address; +pub use findex::Findex; +pub use kv::KvStore; pub use stm::Stm; +pub use value::Value; + +pub const ADDRESS_LENGTH: usize = 16; +pub const KEY_LENGTH: usize = 32; + +/// An index can insert and delete bindings, and search keywords. +pub trait Index<'a, Keyword: Hash, Value: Hash> { + type Error: std::error::Error; + + fn search( + &'a self, + keywords: impl Iterator, + ) -> Result>, Self::Error>; + + fn insert( + &'a self, + bindings: impl Iterator)>, + ) -> Result<(), Self::Error>; + + fn delete(bindings: impl Iterator)>) + -> Result<(), Self::Error>; +} diff --git a/src/obf.rs b/src/obf.rs index 764cb91c..492bca22 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -2,10 +2,10 @@ use std::{ cmp::Ordering, collections::{HashMap, HashSet}, ops::DerefMut, - sync::{Mutex, MutexGuard}, + sync::{Arc, Mutex, MutexGuard}, }; -use crate::{address::Address, error::Error, Stm}; +use crate::{address::Address, error::Error, Stm, ADDRESS_LENGTH, KEY_LENGTH}; use aes::{ cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit}, Aes256, @@ -15,26 +15,23 @@ use cosmian_crypto_core::{ RandomFixedSizeCBytes, Secret, SymmetricKey, }; -const ADDRESS_LENGTH: usize = 16; -const KEY_LENGTH: usize = 32; - -pub struct ObfuscationLayer, Word = Vec>> { +pub struct EncryptionLayer, Word = Vec>> { permutation_key: SymmetricKey, encryption_key: SymmetricKey<32>, - cache: Mutex, Vec), Vec>>, - rng: Mutex, + cache: Arc, Vec), Vec>>>, + rng: Arc>, stm: Memory, } -impl, Word = Vec>> ObfuscationLayer { - pub fn new(seed: Secret, rng: CsRng, stm: Memory) -> Self { +impl, Word = Vec>> EncryptionLayer { + pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { let permutation_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); let encryption_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); Self { permutation_key, encryption_key, - cache: Mutex::new(HashMap::new()), - rng: Mutex::new(rng), + cache: Arc::new(Mutex::new(HashMap::new())), + rng, stm, } } @@ -135,7 +132,7 @@ impl, Word = Vec>> Obfuscation impl< Memory: Stm
, Word = Vec>, // use a `Vec` because `const` generics // are not allowed in `const` operations - > Stm for ObfuscationLayer + > Stm for EncryptionLayer { type Address = Address; @@ -195,14 +192,17 @@ impl< #[cfg(test)] mod tests { - use std::collections::HashSet; + use std::{ + collections::HashSet, + sync::{Arc, Mutex}, + }; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; use crate::{ address::Address, kv::KvStore, - obf::{ObfuscationLayer, ADDRESS_LENGTH}, + obf::{EncryptionLayer, ADDRESS_LENGTH}, stm::Stm, }; @@ -210,8 +210,8 @@ mod tests { fn test_encrypt_decrypt() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::new(); - let obf = ObfuscationLayer::new(seed, rng.clone(), kv); + let kv = KvStore::, Vec>::default(); + let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let tok = Address::::random(&mut rng); let ptx = vec![1]; let ctx = obf.encrypt(&ptx, &tok).unwrap(); @@ -227,8 +227,8 @@ mod tests { fn test_vector_push() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::new(); - let obf = ObfuscationLayer::new(seed, rng.clone(), kv); + let kv = KvStore::, Vec>::default(); + let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let header_addr = Address::::random(&mut rng); diff --git a/src/ovec.rs b/src/ovec.rs index 9edc80ce..abb2e56f 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -64,6 +64,10 @@ impl< Memory: Stm
>, > OVec<'a, Memory> { + pub fn new(a: Address, m: &'a Memory) -> Self { + Self { a, h: None, m } + } + pub fn push(&mut self, values: Vec>) -> Result<(), Error> { let try_push = |old: Option<&Header>| -> Result<(Option
, Header), Error> { @@ -72,11 +76,10 @@ impl< new.stop += values.len() as u64; // Binds the correct addresses to the values. - let mut bindings = values - .iter() - .cloned() - .enumerate() - .map(|(i, v)| (self.a.clone() + new.start + 1 + i as u64, v)) + let mut bindings = (new.stop - values.len() as u64 ..new.stop).zip(values + .clone() + ) + .map(|(i, v)| (self.a.clone() + 1 + i, v)) .collect::>(); bindings.push((self.a.clone(), (&new).into())); @@ -107,15 +110,15 @@ impl< // Read a first batch of addresses: // - the header address; // - the value addresses derived from the known header. - let header = self.h.clone().unwrap_or_default(); - let addresses = (header.start + 1..header.stop + 1) + let old_header = self.h.clone().unwrap_or_default(); + let addresses = (old_header.start + 1..old_header.stop + 1) .chain(0..=0) .map(|i| self.a.clone() + i) .collect(); let mut res = self.m.batch_read(addresses)?; - let current_header = res + let cur_header = res .get(&self.a) .ok_or_else(|| Error::MissingValue(self.a.clone()))? .as_ref() @@ -124,7 +127,7 @@ impl< .map_err(Error::Conversion)? .unwrap_or_default(); - let res = (current_header.start..current_header.stop) + let res = (cur_header.start..cur_header.stop) .map(|i| self.a.clone() + 1 + i) .map(|a| { let v = res.remove(&a).flatten(); @@ -150,82 +153,57 @@ impl< } } -//#[cfg(test)] -//mod tests { -///// Ensures a transaction can express an vector push operation: -///// - the counter is correctly incremented and all values are written; -///// - using the wrong value in the guard fails the operation and returns the current value. -//fn test_vector_push() { -//let mut rng = CsRng::from_entropy(); -//let seed = Secret::random(&mut rng); -//let kv = KvStore::, Vec>::new(); -//let obf = ObfuscationLayer::new(seed, rng.clone(), kv); -//let ovec = OVec::new(seed, rng.clone(), obf); - -//let header_addr = Address::::random(&mut rng); - -//let val_addr_1 = Address::::random(&mut rng); -//let val_addr_2 = Address::::random(&mut rng); -//let val_addr_3 = Address::::random(&mut rng); -//let val_addr_4 = Address::::random(&mut rng); - -//assert_eq!( -//obf.guarded_write( -//(header_addr.clone(), None), -//vec![ -//(header_addr.clone(), vec![2]), -//(val_addr_1.clone(), vec![1]), -//(val_addr_2.clone(), vec![1]) -//] -//) -//.unwrap(), -//None -//); - -//assert_eq!( -//obf.guarded_write( -//(header_addr.clone(), None), -//vec![ -//(header_addr.clone(), vec![2]), -//(val_addr_1.clone(), vec![3]), -//(val_addr_2.clone(), vec![3]) -//] -//) -//.unwrap(), -//Some(vec![2]) -//); - -//assert_eq!( -//obf.guarded_write( -//(header_addr.clone(), Some(vec![2])), -//vec![ -//(header_addr.clone(), vec![4]), -//(val_addr_3.clone(), vec![2]), -//(val_addr_4.clone(), vec![2]) -//] -//) -//.unwrap(), -//Some(vec![2]) -//); - -//assert_eq!( -//HashSet::<(Address, Option>)>::from_iter([ -//(header_addr.clone(), Some(vec![4])), -//(val_addr_1.clone(), Some(vec![1])), -//(val_addr_2.clone(), Some(vec![1])), -//(val_addr_3.clone(), Some(vec![2])), -//(val_addr_4.clone(), Some(vec![2])) -//]), -//HashSet::from_iter( -//obf.batch_read(vec![ -//header_addr, -//val_addr_1, -//val_addr_2, -//val_addr_3, -//val_addr_4 -//]) -//.unwrap() -//), -//) -//} -//} +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + + use crate::{address::Address, kv::KvStore, obf::EncryptionLayer, ovec::OVec, ADDRESS_LENGTH}; + + #[test] + fn test_vector_push_with_shared_cache() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::default(); + let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let address = Address::random(&mut rng); + let mut vector1 = OVec::new(address.clone(), &obf); + let mut vector2 = OVec::new(address.clone(), &obf); + + let values = (0..10).map(|n| vec![n]).collect::>(); + vector1.push(values[..5].to_vec()).unwrap(); + vector2.push(values[..5].to_vec()).unwrap(); + vector1.push(values[5..].to_vec()).unwrap(); + vector2.push(values[5..].to_vec()).unwrap(); + assert_eq!( + [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), + vector1.read().unwrap() + ); + } + + #[test] + fn test_vector_push_without_shared_cache() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::default(); + let obf1 = + EncryptionLayer::new(seed.clone(), Arc::new(Mutex::new(rng.clone())), kv.clone()); + let obf2 = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let address = Address::random(&mut rng); + let mut vector1 = OVec::new(address.clone(), &obf1); + let mut vector2 = OVec::new(address.clone(), &obf2); + + let values = (0..10).map(|n| vec![n]).collect::>(); + vector1.push(values[..5].to_vec()).unwrap(); + // vector2 should fail its first attempt. + vector2.push(values[..5].to_vec()).unwrap(); + vector1.push(values[5..].to_vec()).unwrap(); + // vector2 should fail its first attempt. + vector2.push(values[5..].to_vec()).unwrap(); + assert_eq!( + [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), + vector1.read().unwrap() + ); + } +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 00000000..c0a7b822 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,48 @@ +use std::string::FromUtf8Error; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Value(Vec); + +impl From<&[u8]> for Value { + fn from(value: &[u8]) -> Self { + Self(value.to_vec()) + } +} + +impl From> for Value { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From<&Value> for Vec { + fn from(value: &Value) -> Self { + value.0.to_vec() + } +} + +impl From for Vec { + fn from(value: Value) -> Self { + value.0 + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Self(value.as_bytes().to_vec()) + } +} + +impl TryFrom for String { + type Error = FromUtf8Error; + + fn try_from(value: Value) -> Result { + String::from_utf8(value.0) + } +} + +impl From for Value { + fn from(value: usize) -> Self { + Self(value.to_be_bytes().to_vec()) + } +} From e9c0be340f43230ea71375487abcacbc7c9f7a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 18 Jun 2024 17:44:27 +0200 Subject: [PATCH 04/42] add helpers --- src/findex.rs | 69 ++++++++++++++++++++++++++++++++++++++------------- src/obf.rs | 1 + src/ovec.rs | 14 ++++++++++- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/findex.rs b/src/findex.rs index 7f75f3fb..fdd8cee7 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -22,6 +22,49 @@ impl<'a, Memory: 'a + Stm
, Word = Vec>> Fi vectors: Mutex::new(HashMap::new()), } } + + fn set_vector( + &'a self, + address: Address, + vector: OVec<'a, EncryptionLayer>, + ) { + self.vectors + .lock() + .expect("poisoned mutex") + .insert(address, vector); + } + + fn get_vector( + &'a self, + address: &Address, + ) -> OVec<'a, EncryptionLayer> { + self.vectors + .lock() + .expect("poisoned mutex") + .get(address) + .cloned() + .unwrap_or_else(|| OVec::<'a, EncryptionLayer>::new(address.clone(), &self.el)) + } + + fn hash_address(bytes: &[u8]) -> Address { + let mut a = Address::::default(); + kdf128!(&mut a, bytes); + a + } + + fn decompose(values: impl Iterator) -> Vec> + where + for<'z> Vec: From<&'z Value>, + { + values.map(|v| >::from(&v)).collect() + } + + fn recompose(links: Vec>) -> HashSet + where + Value: Hash + PartialEq + Eq + From>, + { + links.into_iter().map(Value::from).collect() + } } impl< @@ -41,17 +84,11 @@ where ) -> Result>, Self::Error> { keywords .map(|kw| { - let mut a = Address::::default(); - kdf128!(&mut a, kw.as_ref()); - let mut vectors = self.vectors.lock().expect("poisoned mutex"); - let vector = vectors - .entry(a.clone()) - .or_insert_with(|| OVec::<'a, EncryptionLayer>::new(a, &self.el)); + let a = Self::hash_address(kw.as_ref()); + let vector = self.get_vector(&a); let links = vector.read()?; - Ok(( - kw, - links.into_iter().map(Value::from).collect::>(), - )) + self.set_vector(a, vector); + Ok((kw, Self::recompose(links))) }) .collect() } @@ -62,13 +99,11 @@ where ) -> Result<(), Self::Error> { bindings .map(|(kw, vals)| { - let mut a = Address::::default(); - kdf128!(&mut a, kw.as_ref()); - let mut vectors = self.vectors.lock().expect("poisoned mutex"); - let vector = vectors - .entry(a.clone()) - .or_insert_with(|| OVec::<'a, EncryptionLayer>::new(a, &self.el)); - vector.push(vals.iter().map(>::from).collect::>()) + let a = Self::hash_address(kw.as_ref()); + let mut vector = self.get_vector(&a); + vector.push(Self::decompose(vals.into_iter()))?; + self.set_vector(a, vector); + Ok::<(), Self::Error>(()) }) .collect::, _>>()?; Ok(()) diff --git a/src/obf.rs b/src/obf.rs index 492bca22..0990f7be 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -15,6 +15,7 @@ use cosmian_crypto_core::{ RandomFixedSizeCBytes, Secret, SymmetricKey, }; +#[derive(Debug)] pub struct EncryptionLayer, Word = Vec>> { permutation_key: SymmetricKey, encryption_key: SymmetricKey<32>, diff --git a/src/ovec.rs b/src/ovec.rs index abb2e56f..aa7c19b1 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -51,13 +51,25 @@ impl TryFrom<&[u8]> for Header { /// | | /// +------------distant-memory-----------+ /// ``` -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct OVec<'a, Memory: Stm>> { a: Memory::Address, h: Option
, m: &'a Memory, } +impl<'a, Address: Clone, Memory: Stm
>> Clone + for OVec<'a, Memory> +{ + fn clone(&self) -> Self { + Self { + a: self.a.clone(), + h: self.h.clone(), + m: self.m, + } + } +} + impl< 'a, Address: Hash + Eq + Debug + Clone + Add, From 3dd2b5b671cf94a0b5c3368edc6ee226a4ee7247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 18 Jun 2024 18:02:27 +0200 Subject: [PATCH 05/42] add futures --- Cargo.toml | 3 +++ src/dem.rs | 0 src/findex.rs | 73 +++++++++++++++++++++++++++++++++++---------------- src/kv.rs | 20 +++++++------- src/lib.rs | 10 ++++--- src/obf.rs | 25 +++++++++--------- src/ovec.rs | 64 +++++++++++++++++++++++--------------------- src/stm.rs | 5 ++-- 8 files changed, 120 insertions(+), 80 deletions(-) delete mode 100644 src/dem.rs diff --git a/Cargo.toml b/Cargo.toml index a4c209a3..9e37b620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [dependencies] aes = "0.8.4" cosmian_crypto_core = "9.4.0" + +[dev-dependencies] +futures = "0.3.30" diff --git a/src/dem.rs b/src/dem.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/findex.rs b/src/findex.rs index fdd8cee7..635a508d 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -65,6 +65,35 @@ impl<'a, Memory: 'a + Stm
, Word = Vec>> Fi { links.into_iter().map(Value::from).collect() } + + async fn vector_push, Value>( + &'a self, + kw: Keyword, + values: HashSet, + ) -> Result<(), Error, as Stm>::Error>> + where + for<'z> Vec: From<&'z Value>, + { + let a = Self::hash_address(kw.as_ref()); + let mut vector = self.get_vector(&a); + vector.push(Self::decompose(values.into_iter())).await?; + self.set_vector(a, vector); + Ok(()) + } + + async fn read, Value: Hash + PartialEq + Eq + From>>( + &'a self, + kw: Keyword, + ) -> Result< + (Keyword, HashSet), + Error, as Stm>::Error>, + > { + let a = Self::hash_address(kw.as_ref()); + let vector = self.get_vector(&a); + let links = vector.read().await?; + self.set_vector(a, vector); + Ok((kw, Self::recompose(links))) + } } impl< @@ -78,38 +107,35 @@ where { type Error = Error, as Stm>::Error>; - fn search( + async fn search( &'a self, keywords: impl Iterator, ) -> Result>, Self::Error> { - keywords - .map(|kw| { - let a = Self::hash_address(kw.as_ref()); - let vector = self.get_vector(&a); - let links = vector.read()?; - self.set_vector(a, vector); - Ok((kw, Self::recompose(links))) - }) - .collect() + let futures = keywords + .map(|kw| self.read::(kw)) + .collect::>(); + let mut bindings = HashMap::new(); + for fut in futures { + let (kw, vals) = fut.await?; + bindings.insert(kw, vals); + } + Ok(bindings) } - fn insert( + async fn insert( &'a self, bindings: impl Iterator)>, ) -> Result<(), Self::Error> { - bindings - .map(|(kw, vals)| { - let a = Self::hash_address(kw.as_ref()); - let mut vector = self.get_vector(&a); - vector.push(Self::decompose(vals.into_iter()))?; - self.set_vector(a, vector); - Ok::<(), Self::Error>(()) - }) - .collect::, _>>()?; + let futures = bindings + .map(|(kw, vals)| self.vector_push(kw, vals)) + .collect::>(); + for fut in futures { + fut.await?; + } Ok(()) } - fn delete( + async fn delete( _bindings: impl Iterator)>, ) -> Result<(), Self::Error> { todo!() @@ -124,6 +150,7 @@ mod tests { }; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + use futures::executor::block_on; use crate::{address::Address, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH}; @@ -143,8 +170,8 @@ mod tests { HashSet::from_iter([Value::from(0), Value::from(2), Value::from(4)]), ), ]); - findex.insert(bindings.clone().into_iter()).unwrap(); - let res = findex.search(bindings.keys().cloned()).unwrap(); + block_on(findex.insert(bindings.clone().into_iter())).unwrap(); + let res = block_on(findex.search(bindings.keys().cloned())).unwrap(); assert_eq!(bindings, res); } } diff --git a/src/kv.rs b/src/kv.rs index 811378ae..0f766830 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -28,7 +28,10 @@ impl Stm for KvStore) -> Result>, Self::Error> { + async fn batch_read( + &self, + a: Vec
, + ) -> Result>, Self::Error> { let store = &mut *self.0.lock().expect("poisoned lock"); Ok(a.into_iter() .map(|k| { @@ -38,7 +41,7 @@ impl Stm for KvStore), bindings: Vec<(Self::Address, Self::Word)>, @@ -59,6 +62,8 @@ impl Stm for KvStore::default(); assert_eq!( - kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)]) - .unwrap(), + block_on(kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)])).unwrap(), None ); assert_eq!( - kv.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)]) - .unwrap(), + block_on(kv.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)])).unwrap(), Some(2) ); assert_eq!( - kv.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)]) - .unwrap(), + block_on(kv.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)])).unwrap(), Some(2) ); assert_eq!( HashMap::from_iter([(1, Some(1)), (2, Some(1)), (3, Some(3)), (4, Some(3))]), - kv.batch_read(vec![1, 2, 3, 4]).unwrap(), + block_on(kv.batch_read(vec![1, 2, 3, 4])).unwrap(), ) } } diff --git a/src/lib.rs b/src/lib.rs index 437efdcf..a6f0de72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod value; use std::{ collections::{HashMap, HashSet}, + future::Future, hash::Hash, }; @@ -30,13 +31,14 @@ pub trait Index<'a, Keyword: Hash, Value: Hash> { fn search( &'a self, keywords: impl Iterator, - ) -> Result>, Self::Error>; + ) -> impl Future>, Self::Error>>; fn insert( &'a self, bindings: impl Iterator)>, - ) -> Result<(), Self::Error>; + ) -> impl Future>; - fn delete(bindings: impl Iterator)>) - -> Result<(), Self::Error>; + fn delete( + bindings: impl Iterator)>, + ) -> impl Future>; } diff --git a/src/obf.rs b/src/obf.rs index 0990f7be..18efa55a 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -141,7 +141,7 @@ impl< type Error = Error; - fn batch_read( + async fn batch_read( &self, addresses: Vec, ) -> Result>, Self::Error> { @@ -151,7 +151,7 @@ impl< .map(|a| self.permute(a)) .collect(); - let bindings = self.stm.batch_read(tokens)?; + let bindings = self.stm.batch_read(tokens).await?; bindings .into_iter() @@ -163,7 +163,7 @@ impl< .collect() } - fn guarded_write( + async fn guarded_write( &self, guard: (Self::Address, Option), bindings: Vec<(Self::Address, Self::Word)>, @@ -180,7 +180,7 @@ impl< }) .collect::, _>>()?; - let ctx = self.stm.guarded_write((tok.clone(), old), bindings)?; + let ctx = self.stm.guarded_write((tok.clone(), old), bindings).await?; if let Some(ctx) = ctx { let ptx = self.decrypt(ctx.clone(), tok)?; @@ -199,6 +199,7 @@ mod tests { }; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + use futures::executor::block_on; use crate::{ address::Address, @@ -239,40 +240,40 @@ mod tests { let val_addr_4 = Address::::random(&mut rng); assert_eq!( - obf.guarded_write( + block_on(obf.guarded_write( (header_addr.clone(), None), vec![ (header_addr.clone(), vec![2]), (val_addr_1.clone(), vec![1]), (val_addr_2.clone(), vec![1]) ] - ) + )) .unwrap(), None ); assert_eq!( - obf.guarded_write( + block_on(obf.guarded_write( (header_addr.clone(), None), vec![ (header_addr.clone(), vec![2]), (val_addr_1.clone(), vec![3]), (val_addr_2.clone(), vec![3]) ] - ) + )) .unwrap(), Some(vec![2]) ); assert_eq!( - obf.guarded_write( + block_on(obf.guarded_write( (header_addr.clone(), Some(vec![2])), vec![ (header_addr.clone(), vec![4]), (val_addr_3.clone(), vec![2]), (val_addr_4.clone(), vec![2]) ] - ) + )) .unwrap(), Some(vec![2]) ); @@ -286,13 +287,13 @@ mod tests { (val_addr_4.clone(), Some(vec![2])) ]), HashSet::from_iter( - obf.batch_read(vec![ + block_on(obf.batch_read(vec![ header_addr, val_addr_1, val_addr_2, val_addr_3, val_addr_4 - ]) + ])) .unwrap() ), ) diff --git a/src/ovec.rs b/src/ovec.rs index aa7c19b1..80493963 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -80,17 +80,20 @@ impl< Self { a, h: None, m } } - pub fn push(&mut self, values: Vec>) -> Result<(), Error> { - let try_push = - |old: Option<&Header>| -> Result<(Option
, Header), Error> { + pub async fn push( + &mut self, + values: Vec>, + ) -> Result<(), Error> { + loop { + let old = self.h.as_ref(); + let (cur, new) = { // Generates a new header which counter is incremented. let mut new = old.cloned().unwrap_or_default(); new.stop += values.len() as u64; // Binds the correct addresses to the values. - let mut bindings = (new.stop - values.len() as u64 ..new.stop).zip(values - .clone() - ) + let mut bindings = (new.stop - values.len() as u64..new.stop) + .zip(values.clone()) .map(|(i, v)| (self.a.clone() + 1 + i, v)) .collect::>(); bindings.push((self.a.clone(), (&new).into())); @@ -98,17 +101,14 @@ impl< // Attempts committing the new bindings using the old header as guard. let cur = self .m - .guarded_write((self.a.clone(), old.map(>::from)), bindings)? + .guarded_write((self.a.clone(), old.map(>::from)), bindings) + .await? .map(|v| Header::try_from(v.as_slice())) .transpose() .map_err(Error::Conversion)?; - Ok((cur, new)) + (cur, new) }; - - loop { - let old = self.h.as_ref(); - let (cur, new) = try_push(old)?; if cur.as_ref() == old { self.h = Some(new); return Ok(()); @@ -118,7 +118,7 @@ impl< } } - pub fn read(&self) -> Result>, Error> { + pub async fn read(&self) -> Result>, Error> { // Read a first batch of addresses: // - the header address; // - the value addresses derived from the known header. @@ -128,7 +128,7 @@ impl< .map(|i| self.a.clone() + i) .collect(); - let mut res = self.m.batch_read(addresses)?; + let mut res = self.m.batch_read(addresses).await?; let cur_header = res .get(&self.a) @@ -148,12 +148,15 @@ impl< .collect::>(); // Read missing values if any. - let mut missing_res = self.m.batch_read( - res.iter() - .filter_map(|(a, v)| if v.is_none() { Some(a) } else { None }) - .cloned() - .collect(), - )?; + let mut missing_res = self + .m + .batch_read( + res.iter() + .filter_map(|(a, v)| if v.is_none() { Some(a) } else { None }) + .cloned() + .collect(), + ) + .await?; res.into_iter() .map(|(a, maybe_v)| { @@ -167,6 +170,7 @@ impl< #[cfg(test)] mod tests { + use futures::executor::block_on; use std::sync::{Arc, Mutex}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; @@ -184,13 +188,13 @@ mod tests { let mut vector2 = OVec::new(address.clone(), &obf); let values = (0..10).map(|n| vec![n]).collect::>(); - vector1.push(values[..5].to_vec()).unwrap(); - vector2.push(values[..5].to_vec()).unwrap(); - vector1.push(values[5..].to_vec()).unwrap(); - vector2.push(values[5..].to_vec()).unwrap(); + block_on(vector1.push(values[..5].to_vec())).unwrap(); + block_on(vector2.push(values[..5].to_vec())).unwrap(); + block_on(vector1.push(values[5..].to_vec())).unwrap(); + block_on(vector2.push(values[5..].to_vec())).unwrap(); assert_eq!( [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), - vector1.read().unwrap() + block_on(vector1.read()).unwrap() ); } @@ -207,15 +211,15 @@ mod tests { let mut vector2 = OVec::new(address.clone(), &obf2); let values = (0..10).map(|n| vec![n]).collect::>(); - vector1.push(values[..5].to_vec()).unwrap(); + block_on(vector1.push(values[..5].to_vec())).unwrap(); // vector2 should fail its first attempt. - vector2.push(values[..5].to_vec()).unwrap(); - vector1.push(values[5..].to_vec()).unwrap(); + block_on(vector2.push(values[..5].to_vec())).unwrap(); + block_on(vector1.push(values[5..].to_vec())).unwrap(); // vector2 should fail its first attempt. - vector2.push(values[5..].to_vec()).unwrap(); + block_on(vector2.push(values[5..].to_vec())).unwrap(); assert_eq!( [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), - vector1.read().unwrap() + block_on(vector1.read()).unwrap() ); } } diff --git a/src/stm.rs b/src/stm.rs index 184db396..48a6c858 100644 --- a/src/stm.rs +++ b/src/stm.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::future::Future; pub trait Stm { /// Address space. @@ -14,7 +15,7 @@ pub trait Stm { fn batch_read( &self, a: Vec, - ) -> Result>, Self::Error>; + ) -> impl Future>, Self::Error>>; /// Adds the given memory bindings if the guard binding is stored. /// Returns the value of the guarded word after the writes. @@ -22,5 +23,5 @@ pub trait Stm { &self, guard: (Self::Address, Option), bindings: Vec<(Self::Address, Self::Word)>, - ) -> Result, Self::Error>; + ) -> impl Future, Self::Error>>; } From b167b0cd2acb5163f025bb85f11a1700ddc897e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 18 Jun 2024 18:36:00 +0200 Subject: [PATCH 06/42] remove unused crypto_core features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e37b620..5cf994c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] aes = "0.8.4" -cosmian_crypto_core = "9.4.0" +cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3"]} [dev-dependencies] futures = "0.3.30" From cb3d47506bf3a9684da04671523a566862b3d356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 25 Jun 2024 16:50:22 +0200 Subject: [PATCH 07/42] cleanup commit: - add comments - refacto some code - remove useless shuffle operation in the encryption layer - do some renaming --- src/findex.rs | 119 ++++++++++++++++++++++++++++++++------------------ src/kv.rs | 15 ++----- src/lib.rs | 8 +++- src/obf.rs | 111 +++++++++++++++++++--------------------------- src/ovec.rs | 65 +++++++++++++-------------- src/stm.rs | 12 ++--- src/value.rs | 5 +++ 7 files changed, 175 insertions(+), 160 deletions(-) diff --git a/src/findex.rs b/src/findex.rs index 635a508d..d9085525 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -7,26 +7,38 @@ use std::{ use cosmian_crypto_core::{kdf128, CsRng, Secret}; use crate::{ - error::Error, obf::EncryptionLayer, ovec::OVec, Address, Index, Stm, ADDRESS_LENGTH, KEY_LENGTH, + error::Error, obf::MemoryEncryptionLayer, ovec::OVec, Address, Index, Stm, ADDRESS_LENGTH, + KEY_LENGTH, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Op { + Insert, + Delete, +} + pub struct Findex<'a, Memory: 'a + Stm
, Word = Vec>> { - el: EncryptionLayer, - vectors: Mutex, OVec<'a, EncryptionLayer>>>, + el: MemoryEncryptionLayer, + vectors: Mutex, OVec<'a, MemoryEncryptionLayer>>>, } impl<'a, Memory: 'a + Stm
, Word = Vec>> Findex<'a, Memory> { + /// Instantiates Findex with the given seed, and memory. pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { + // TODO: should the RNG be instantiated here? + // Creating many instances of Findex would need more work but potentially involve less + // waiting for the lock => bench it. Self { - el: EncryptionLayer::new(seed, rng, stm), + el: MemoryEncryptionLayer::new(seed, rng, stm), vectors: Mutex::new(HashMap::new()), } } - fn set_vector( + /// Caches this vector for this address. + fn bind( &'a self, address: Address, - vector: OVec<'a, EncryptionLayer>, + vector: OVec<'a, MemoryEncryptionLayer>, ) { self.vectors .lock() @@ -34,16 +46,16 @@ impl<'a, Memory: 'a + Stm
, Word = Vec>> Fi .insert(address, vector); } - fn get_vector( - &'a self, + /// Retrieves the vector cached for this address, if any. + fn find( + &self, address: &Address, - ) -> OVec<'a, EncryptionLayer> { + ) -> Option>> { self.vectors .lock() .expect("poisoned mutex") .get(address) .cloned() - .unwrap_or_else(|| OVec::<'a, EncryptionLayer>::new(address.clone(), &self.el)) } fn hash_address(bytes: &[u8]) -> Address { @@ -52,47 +64,69 @@ impl<'a, Memory: 'a + Stm
, Word = Vec>> Fi a } - fn decompose(values: impl Iterator) -> Vec> + fn decompose(_op: Op, values: impl Iterator) -> Vec> where for<'z> Vec: From<&'z Value>, { values.map(|v| >::from(&v)).collect() } - fn recompose(links: Vec>) -> HashSet - where - Value: Hash + PartialEq + Eq + From>, + fn recompose>>( + words: Vec>, + ) -> HashSet { + words.into_iter().map(Value::from).collect() + } + + /// Pushes the given bindings to the vectors associated to the bound keyword. + /// + /// All vector push operations are performed in parallel (via async calls), not batched. + async fn push>( + &'a self, + bindings: impl Iterator>)>, + ) -> Result<(), Error, as Stm>::Error>> { - links.into_iter().map(Value::from).collect() + let futures = bindings + .map(|(kw, vals)| self.vector_push(kw, vals)) + .collect::>(); // collect for calls do be made + + for fut in futures { + fut.await?; + } + + Ok(()) } - async fn vector_push, Value>( + // TODO: move into push as an async closure when stable. + async fn vector_push>( &'a self, kw: Keyword, - values: HashSet, - ) -> Result<(), Error, as Stm>::Error>> - where - for<'z> Vec: From<&'z Value>, + values: Vec>, + ) -> Result<(), Error, as Stm>::Error>> { let a = Self::hash_address(kw.as_ref()); - let mut vector = self.get_vector(&a); - vector.push(Self::decompose(values.into_iter())).await?; - self.set_vector(a, vector); + let mut vector = self + .find(&a) + .unwrap_or_else(|| OVec::new(a.clone(), &self.el)); + vector.push(values).await?; + self.bind(a, vector); Ok(()) } - async fn read, Value: Hash + PartialEq + Eq + From>>( + // TODO: move into search as an async closure when stable. + async fn read>( &'a self, kw: Keyword, ) -> Result< - (Keyword, HashSet), - Error, as Stm>::Error>, + (Keyword, Vec>), + Error, as Stm>::Error>, > { let a = Self::hash_address(kw.as_ref()); - let vector = self.get_vector(&a); - let links = vector.read().await?; - self.set_vector(a, vector); - Ok((kw, Self::recompose(links))) + let vector = self + .find(&a) + .unwrap_or_else(|| OVec::new(a.clone(), &self.el)); + let words = vector.read().await?; + self.bind(a, vector); + Ok((kw, words)) } } @@ -105,19 +139,19 @@ impl< where for<'z> Vec: From<&'z Value>, { - type Error = Error, as Stm>::Error>; + type Error = Error, as Stm>::Error>; async fn search( &'a self, keywords: impl Iterator, ) -> Result>, Self::Error> { let futures = keywords - .map(|kw| self.read::(kw)) + .map(|kw| self.read::(kw)) .collect::>(); let mut bindings = HashMap::new(); for fut in futures { let (kw, vals) = fut.await?; - bindings.insert(kw, vals); + bindings.insert(kw, Self::recompose(vals)); } Ok(bindings) } @@ -126,19 +160,20 @@ where &'a self, bindings: impl Iterator)>, ) -> Result<(), Self::Error> { - let futures = bindings - .map(|(kw, vals)| self.vector_push(kw, vals)) - .collect::>(); - for fut in futures { - fut.await?; - } - Ok(()) + self.push( + bindings.map(|(kw, values)| (kw, Self::decompose(Op::Insert, values.into_iter()))), + ) + .await } async fn delete( - _bindings: impl Iterator)>, + &'a self, + bindings: impl Iterator)>, ) -> Result<(), Self::Error> { - todo!() + self.push( + bindings.map(|(kw, values)| (kw, Self::decompose(Op::Delete, values.into_iter()))), + ) + .await } } diff --git a/src/kv.rs b/src/kv.rs index 0f766830..ba24abb1 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -28,17 +28,9 @@ impl Stm for KvStore, - ) -> Result>, Self::Error> { + async fn batch_read(&self, a: Vec
) -> Result>, Self::Error> { let store = &mut *self.0.lock().expect("poisoned lock"); - Ok(a.into_iter() - .map(|k| { - let v = store.get(&k).cloned(); - (k, v) - }) - .collect()) + Ok(a.into_iter().map(|k| store.get(&k).cloned()).collect()) } async fn guarded_write( @@ -60,7 +52,6 @@ impl Stm for KvStore { type Error: std::error::Error; + /// Search the index for the values bound to the given keywords. fn search( &'a self, keywords: impl Iterator, ) -> impl Future>, Self::Error>>; + /// Add the given bindings to the index. fn insert( &'a self, bindings: impl Iterator)>, ) -> impl Future>; + /// Remove the given bindings from the index. fn delete( + &'a self, bindings: impl Iterator)>, ) -> impl Future>; } diff --git a/src/obf.rs b/src/obf.rs index 18efa55a..afe219f9 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -1,5 +1,4 @@ use std::{ - cmp::Ordering, collections::{HashMap, HashSet}, ops::DerefMut, sync::{Arc, Mutex, MutexGuard}, @@ -11,12 +10,12 @@ use aes::{ Aes256, }; use cosmian_crypto_core::{ - reexport::rand_core::RngCore, Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, - RandomFixedSizeCBytes, Secret, SymmetricKey, + Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, Secret, + SymmetricKey, }; #[derive(Debug)] -pub struct EncryptionLayer, Word = Vec>> { +pub struct MemoryEncryptionLayer, Word = Vec>> { permutation_key: SymmetricKey, encryption_key: SymmetricKey<32>, cache: Arc, Vec), Vec>>>, @@ -24,7 +23,10 @@ pub struct EncryptionLayer, Word = stm: Memory, } -impl, Word = Vec>> EncryptionLayer { +// NOTE: cannot enforce word length at compile time without hard-coding numbers since constant +// generics are not yet allowed in constant operations. +impl, Word = Vec>> MemoryEncryptionLayer { + /// Instantiates a new memory encryption layer. pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { let permutation_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); let encryption_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); @@ -37,12 +39,13 @@ impl, Word = Vec>> EncryptionL } } - pub fn rng(&self) -> MutexGuard { + #[inline(always)] + fn rng(&self) -> MutexGuard { self.rng.lock().expect("poisoned lock") } /// Retains values cached for the given keys only. - pub fn retain_cached_keys(&self, keys: &HashSet<(Address, Vec)>) { + pub fn retain_cached_keys(&self, keys: &HashSet<(Memory::Address, Vec)>) { self.cache .lock() .expect("poisoned mutex") @@ -50,24 +53,12 @@ impl, Word = Vec>> EncryptionL .retain(|k, _| keys.contains(k)); } - /// Shuffles the given list of values. - fn shuffle(&self, mut v: Vec) -> Vec { - v.sort_by(|_, _| { - if self.rng().next_u32() % 2 == 0 { - Ordering::Less - } else { - Ordering::Greater - } - }); - v - } - - /// Get the encrypted value from the cache, compute it upon cache miss. + /// Gets the encrypted value from the cache and computes it upon cache miss. pub fn encrypt( &self, ptx: &[u8], - tok: &Address, - ) -> Result, Error, Memory::Error>> { + tok: &Memory::Address, + ) -> Result, Error> { let k = (tok.clone(), ptx.to_vec()); if let Some(ctx) = self.find(&k) { Ok(ctx) @@ -82,12 +73,12 @@ impl, Word = Vec>> EncryptionL } } - /// Decrypt the given value, and cache the ciphertext. + /// Decrypts the given value and caches the ciphertext. pub fn decrypt( &self, ctx: Vec, - tok: Address, - ) -> Result, Error, Memory::Error>> { + tok: Memory::Address, + ) -> Result, Error> { if ctx.len() < Aes256Gcm::NONCE_LENGTH { return Err(Error::Parsing("ciphertext too small".to_string())); } @@ -100,19 +91,19 @@ impl, Word = Vec>> EncryptionL Ok(ptx) } - pub fn reorder(&self, mut a: Address) -> Address { + pub fn reorder(&self, mut a: Memory::Address) -> Memory::Address { Aes256::new(GenericArray::from_slice(&self.permutation_key)) .decrypt_block(GenericArray::from_mut_slice(&mut a)); a } - pub fn permute(&self, mut a: Address) -> Address { + pub fn permute(&self, mut a: Memory::Address) -> Memory::Address { Aes256::new(GenericArray::from_slice(&self.permutation_key)) .encrypt_block(GenericArray::from_mut_slice(&mut a)); a } - pub fn bind(&self, k: (Address, Vec), v: Vec) { + pub fn bind(&self, k: (Memory::Address, Vec), v: Vec) { self.cache .lock() .expect("poisoned lock") @@ -133,7 +124,7 @@ impl, Word = Vec>> EncryptionL impl< Memory: Stm
, Word = Vec>, // use a `Vec` because `const` generics // are not allowed in `const` operations - > Stm for EncryptionLayer + > Stm for MemoryEncryptionLayer { type Address = Address; @@ -144,22 +135,13 @@ impl< async fn batch_read( &self, addresses: Vec, - ) -> Result>, Self::Error> { - let tokens = self - .shuffle(addresses) - .into_iter() - .map(|a| self.permute(a)) - .collect(); - - let bindings = self.stm.batch_read(tokens).await?; - + ) -> Result>, Self::Error> { + let tokens: Vec<_> = addresses.into_iter().map(|a| self.permute(a)).collect(); + let bindings = self.stm.batch_read(tokens.clone()).await?; bindings .into_iter() - .map(|(tok, ctx)| { - ctx.map(|ctx| self.decrypt(ctx, tok.clone())) - .transpose() - .map(|maybe_ctx| (self.reorder(tok), maybe_ctx)) - }) + .zip(tokens) + .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(ctx, tok.clone())).transpose()) .collect() } @@ -193,10 +175,7 @@ impl< #[cfg(test)] mod tests { - use std::{ - collections::HashSet, - sync::{Arc, Mutex}, - }; + use std::sync::{Arc, Mutex}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; use futures::executor::block_on; @@ -204,7 +183,7 @@ mod tests { use crate::{ address::Address, kv::KvStore, - obf::{EncryptionLayer, ADDRESS_LENGTH}, + obf::{MemoryEncryptionLayer, ADDRESS_LENGTH}, stm::Stm, }; @@ -213,7 +192,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let tok = Address::::random(&mut rng); let ptx = vec![1]; let ctx = obf.encrypt(&ptx, &tok).unwrap(); @@ -230,7 +209,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let header_addr = Address::::random(&mut rng); @@ -279,23 +258,21 @@ mod tests { ); assert_eq!( - HashSet::<(Address, Option>)>::from_iter([ - (header_addr.clone(), Some(vec![4])), - (val_addr_1.clone(), Some(vec![1])), - (val_addr_2.clone(), Some(vec![1])), - (val_addr_3.clone(), Some(vec![2])), - (val_addr_4.clone(), Some(vec![2])) - ]), - HashSet::from_iter( - block_on(obf.batch_read(vec![ - header_addr, - val_addr_1, - val_addr_2, - val_addr_3, - val_addr_4 - ])) - .unwrap() - ), + vec![ + Some(vec![4]), + Some(vec![1]), + Some(vec![1]), + Some(vec![2]), + Some(vec![2]) + ], + block_on(obf.batch_read(vec![ + header_addr, + val_addr_1, + val_addr_2, + val_addr_3, + val_addr_4 + ])) + .unwrap() ) } } diff --git a/src/ovec.rs b/src/ovec.rs index 80493963..7425ac59 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -76,10 +76,13 @@ impl< Memory: Stm
>, > OVec<'a, Memory> { + /// (Lazily) instantiates a new vector at this address in this memory: no value is written + /// before the first push. pub fn new(a: Address, m: &'a Memory) -> Self { Self { a, h: None, m } } + /// Atomically pushes the given values at the end of this vector. Retries upon conflict. pub async fn push( &mut self, values: Vec>, @@ -114,56 +117,52 @@ impl< return Ok(()); } else { self.h = cur; + // Findex modifications are only lock-free, hence it does not guarantee a given + // client will ever terminate. It arguably will if the index is not highly + // contended, but we need a stronger guarantee. Maybe a return with an error after + // a reaching a certain number of retries. } } } + /// Atomically reads the values stored in this vector. pub async fn read(&self) -> Result>, Error> { - // Read a first batch of addresses: + // Read from a first batch of addresses: // - the header address; // - the value addresses derived from the known header. let old_header = self.h.clone().unwrap_or_default(); - let addresses = (old_header.start + 1..old_header.stop + 1) - .chain(0..=0) - .map(|i| self.a.clone() + i) + let addresses = [self.a.clone()] + .into_iter() + .chain((old_header.start..old_header.stop).map(|i| self.a.clone() + i + 1)) .collect(); - let mut res = self.m.batch_read(addresses).await?; + let res = self.m.batch_read(addresses).await?; - let cur_header = res - .get(&self.a) - .ok_or_else(|| Error::MissingValue(self.a.clone()))? - .as_ref() - .map(|v| Header::try_from(v.as_slice())) + let cur_header = res[0] + .clone() + .map(|v| { + println!("{v:?}"); + Header::try_from(v.as_slice()) + }) .transpose() .map_err(Error::Conversion)? .unwrap_or_default(); - let res = (cur_header.start..cur_header.stop) - .map(|i| self.a.clone() + 1 + i) - .map(|a| { - let v = res.remove(&a).flatten(); - (a, v) - }) - .collect::>(); - - // Read missing values if any. - let mut missing_res = self + // Get all missing values, if any. + let missing_res = self .m .batch_read( - res.iter() - .filter_map(|(a, v)| if v.is_none() { Some(a) } else { None }) - .cloned() + (cur_header.start.max(old_header.stop)..cur_header.stop) + .map(|i| self.a.clone() + i + 1) .collect(), ) .await?; res.into_iter() - .map(|(a, maybe_v)| { - maybe_v - .or_else(|| missing_res.remove(&a).flatten()) - .ok_or_else(|| Error::MissingValue(a.clone())) - }) + .skip(1) + .chain(missing_res) + .enumerate() + .map(|(i, v)| v.ok_or_else(|| Error::MissingValue(self.a.clone() + i as u64))) .collect() } } @@ -175,14 +174,16 @@ mod tests { use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; - use crate::{address::Address, kv::KvStore, obf::EncryptionLayer, ovec::OVec, ADDRESS_LENGTH}; + use crate::{ + address::Address, kv::KvStore, obf::MemoryEncryptionLayer, ovec::OVec, ADDRESS_LENGTH, + }; #[test] fn test_vector_push_with_shared_cache() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let address = Address::random(&mut rng); let mut vector1 = OVec::new(address.clone(), &obf); let mut vector2 = OVec::new(address.clone(), &obf); @@ -204,8 +205,8 @@ mod tests { let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); let obf1 = - EncryptionLayer::new(seed.clone(), Arc::new(Mutex::new(rng.clone())), kv.clone()); - let obf2 = EncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + MemoryEncryptionLayer::new(seed.clone(), Arc::new(Mutex::new(rng.clone())), kv.clone()); + let obf2 = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); let address = Address::random(&mut rng); let mut vector1 = OVec::new(address.clone(), &obf1); let mut vector2 = OVec::new(address.clone(), &obf2); diff --git a/src/stm.rs b/src/stm.rs index 48a6c858..11b90612 100644 --- a/src/stm.rs +++ b/src/stm.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; use std::future::Future; +/// A Software Transactional Memory: all operations exposed are atomic. pub trait Stm { /// Address space. type Address; @@ -11,17 +11,17 @@ pub trait Stm { /// Memory error. type Error: std::error::Error; - /// Reads the words bound to the given addresses. + /// Reads the words from the given addresses. fn batch_read( &self, a: Vec, - ) -> impl Future>, Self::Error>>; + ) -> impl Future>, Self::Error>>; - /// Adds the given memory bindings if the guard binding is stored. - /// Returns the value of the guarded word after the writes. + /// Write the given words at the given addresses if the word currently stored at the guard + /// address is the one given, and returns this guard word. fn guarded_write( &self, guard: (Self::Address, Option), - bindings: Vec<(Self::Address, Self::Word)>, + tasks: Vec<(Self::Address, Self::Word)>, ) -> impl Future, Self::Error>>; } diff --git a/src/value.rs b/src/value.rs index c0a7b822..c35c063e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,3 +1,8 @@ +//! Boring implementation of a typed byte-vector. + +// TODO: Maybe turn all that in a macro (maybe in CryptoCore?) to reuse it for the words and maybe +// others. + use std::string::FromUtf8Error; #[derive(Debug, Clone, Hash, PartialEq, Eq)] From 0e25e2dbeb9f59383f83be1cec4590923b9ef13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 26 Jun 2024 10:21:13 +0200 Subject: [PATCH 08/42] modularize encode/decode --- src/findex.rs | 82 +++++++++++++++++++++++++++++++++------------------ src/ovec.rs | 27 +++++++++-------- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/findex.rs b/src/findex.rs index d9085525..b39435ee 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -12,25 +12,45 @@ use crate::{ }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Op { +pub enum Op { Insert, Delete, } -pub struct Findex<'a, Memory: 'a + Stm
, Word = Vec>> { +// Lifetime is needed to store a reference of the memory in the vectors. +pub struct Findex<'a, Value, Memory: 'a + Stm
, Word = Vec>> +where + Value: TryFrom>, + Vec: From, +{ el: MemoryEncryptionLayer, vectors: Mutex, OVec<'a, MemoryEncryptionLayer>>>, + encode: Box) -> Vec>>, + decode: Box>) -> HashSet>, } -impl<'a, Memory: 'a + Stm
, Word = Vec>> Findex<'a, Memory> { +impl<'a, Value, Memory: 'a + Stm
, Word = Vec>> + Findex<'a, Value, Memory> +where + Value: TryFrom>, + Vec: From, +{ /// Instantiates Findex with the given seed, and memory. - pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { + pub fn new( + seed: Secret, + rng: Arc>, + stm: Memory, + encode: Box) -> Vec>>, + decode: Box>) -> HashSet>, + ) -> Self { // TODO: should the RNG be instantiated here? // Creating many instances of Findex would need more work but potentially involve less // waiting for the lock => bench it. Self { el: MemoryEncryptionLayer::new(seed, rng, stm), vectors: Mutex::new(HashMap::new()), + encode, + decode, } } @@ -64,19 +84,6 @@ impl<'a, Memory: 'a + Stm
, Word = Vec>> Fi a } - fn decompose(_op: Op, values: impl Iterator) -> Vec> - where - for<'z> Vec: From<&'z Value>, - { - values.map(|v| >::from(&v)).collect() - } - - fn recompose>>( - words: Vec>, - ) -> HashSet { - words.into_iter().map(Value::from).collect() - } - /// Pushes the given bindings to the vectors associated to the bound keyword. /// /// All vector push operations are performed in parallel (via async calls), not batched. @@ -135,9 +142,10 @@ impl< Keyword: Hash + PartialEq + Eq + AsRef<[u8]>, Value: Hash + PartialEq + Eq + From>, Memory: 'a + Stm
, Word = Vec>, - > Index<'a, Keyword, Value> for Findex<'a, Memory> + > Index<'a, Keyword, Value> for Findex<'a, Value, Memory> where - for<'z> Vec: From<&'z Value>, + Value: TryFrom>, + Vec: From, { type Error = Error, as Stm>::Error>; @@ -151,7 +159,7 @@ where let mut bindings = HashMap::new(); for fut in futures { let (kw, vals) = fut.await?; - bindings.insert(kw, Self::recompose(vals)); + bindings.insert(kw, (self.decode)(vals)); } Ok(bindings) } @@ -160,20 +168,16 @@ where &'a self, bindings: impl Iterator)>, ) -> Result<(), Self::Error> { - self.push( - bindings.map(|(kw, values)| (kw, Self::decompose(Op::Insert, values.into_iter()))), - ) - .await + self.push(bindings.map(|(kw, values)| (kw, (self.encode)(Op::Insert, values)))) + .await } async fn delete( &'a self, bindings: impl Iterator)>, ) -> Result<(), Self::Error> { - self.push( - bindings.map(|(kw, values)| (kw, Self::decompose(Op::Delete, values.into_iter()))), - ) - .await + self.push(bindings.map(|(kw, values)| (kw, (self.encode)(Op::Delete, values)))) + .await } } @@ -181,6 +185,7 @@ where mod tests { use std::{ collections::{HashMap, HashSet}, + hash::Hash, sync::{Arc, Mutex}, }; @@ -189,12 +194,31 @@ mod tests { use crate::{address::Address, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH}; + use super::Op; + + fn encode(_op: Op, values: HashSet) -> Vec> + where + for<'z> Vec: From<&'z Value>, + { + values.into_iter().map(|v| >::from(&v)).collect() + } + + fn decode>>(words: Vec>) -> HashSet { + words.into_iter().map(Value::from).collect() + } + #[test] fn test_insert_search() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let findex = Findex::new(seed, Arc::new(Mutex::new(rng)), kv); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + kv, + Box::new(encode), + Box::new(decode), + ); let bindings = HashMap::<&str, HashSet>::from_iter([ ( "cat", diff --git a/src/ovec.rs b/src/ovec.rs index 7425ac59..9dcaa4b5 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -118,9 +118,11 @@ impl< } else { self.h = cur; // Findex modifications are only lock-free, hence it does not guarantee a given - // client will ever terminate. It arguably will if the index is not highly - // contended, but we need a stronger guarantee. Maybe a return with an error after - // a reaching a certain number of retries. + // client will ever terminate. + // + // TODO: this loop will arguably terminate if the index is not highly contended, + // but we need a stronger guarantee. Maybe a return with an error after a reaching + // a certain number of retries. } } } @@ -149,18 +151,19 @@ impl< .unwrap_or_default(); // Get all missing values, if any. - let missing_res = self - .m - .batch_read( - (cur_header.start.max(old_header.stop)..cur_header.stop) - .map(|i| self.a.clone() + i + 1) - .collect(), - ) - .await?; + let missing_addresses = (cur_header.start.max(old_header.stop)..cur_header.stop) + .map(|i| self.a.clone() + i + 1) + .collect::>(); + + let missing_values = if missing_addresses.is_empty() { + vec![] // only call the memory a second time if needed + } else { + self.m.batch_read(missing_addresses).await? + }; res.into_iter() .skip(1) - .chain(missing_res) + .chain(missing_values) .enumerate() .map(|(i, v)| v.ok_or_else(|| Error::MissingValue(self.a.clone() + i as u64))) .collect() From 820ff7952a214b1c4f19d9984dddd7e65b1aafbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 1 Jul 2024 09:03:54 +0200 Subject: [PATCH 09/42] Test with dummy encodings --- Cargo.toml | 1 + src/encoding.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++++++ src/findex.rs | 107 +++++++++++++++++++++++++++++--------------- src/lib.rs | 1 + 4 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 src/encoding.rs diff --git a/Cargo.toml b/Cargo.toml index 5cf994c9..a8f1136a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ cosmian_crypto_core = {version = "9.4.0", default-features = false, features = [ [dev-dependencies] futures = "0.3.30" +cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3" , "ser"]} diff --git a/src/encoding.rs b/src/encoding.rs new file mode 100644 index 00000000..badaaddf --- /dev/null +++ b/src/encoding.rs @@ -0,0 +1,116 @@ +#![allow(dead_code)] + +use std::{cmp::Ordering, collections::HashSet}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Op { + Insert, + Delete, +} + +pub enum Mode { + EqBlock(usize), + Offset(usize), +} + +const BLOCK_LENGTH: usize = 16; +const CHUNK_LENGTH: usize = 8 * BLOCK_LENGTH; + +fn is_admissible_eq_block_mode_value(v: &[u8]) -> bool { + (v.len() % BLOCK_LENGTH == 0) && (v.len() < CHUNK_LENGTH) +} + +fn is_eq_block_mode(_vs: &[Vec]) -> Mode { + todo!() +} + +pub(crate) fn encode>>(_op: Op, vs: HashSet) -> Vec> { + let mut serialized_values = vs.into_iter().map(Into::into).collect::>(); + // We sort the values in order to maximize the number of block mode chunks. + // This is an educated guess, maybe work on a quantification. + serialized_values.sort_by(|lhs, rhs| { + if lhs.len() < rhs.len() { + Ordering::Less + } else { + Ordering::Greater + } + }); + + // Use a greedy algorithm to generate chunks. + todo!() +} + +pub(crate) fn decode>>(_ws: Vec>) -> HashSet { + todo!() +} + +#[cfg(test)] +mod tests { + use cosmian_crypto_core::{ + bytes_ser_de::to_leb128_len, + reexport::rand_core::{CryptoRngCore, SeedableRng}, + CsRng, + }; + + use super::*; + + #[test] + fn test_encode_decode_uuids() { + fn random_uuid(rng: &mut impl CryptoRngCore) -> [u8; 16] { + let mut res = [0; 16]; + rng.fill_bytes(&mut res); + res + } + + fn generate_uuids(n: usize) -> Vec<[u8; 16]> { + let mut rng = CsRng::from_entropy(); + (0..n).map(|_| random_uuid(&mut rng)).collect() + } + + fn compute_expected_length(values: &HashSet<[u8; 16]>) -> usize { + (values.len() as f64 / 8.0).ceil() as usize + } + + for n in 0..100 { + let values = HashSet::from_iter(generate_uuids(n)); + let words = encode(Op::Insert, values.clone()); + assert_eq!(words.len(), compute_expected_length(&values)); + let res = decode(words); + assert_eq!(values, res); + } + } + + #[test] + fn test_encode_decode_variable_length_values() { + fn random_value(rng: &mut impl CryptoRngCore, max_length: usize) -> Vec { + let length = rng.next_u64() as usize % max_length; + let mut res = vec![0; length]; + rng.fill_bytes(&mut res); + res + } + + fn generate_uuids(n: usize, max_length: usize) -> Vec> { + let mut rng = CsRng::from_entropy(); + (0..n).map(|_| random_value(&mut rng, max_length)).collect() + } + + fn compute_expected_length(values: &HashSet>) -> usize { + let total_length = values + .iter() + .map(Vec::len) + .map(|l| to_leb128_len(l) + l) + .sum::(); + (total_length as f64 / (8 * 16) as f64).ceil() as usize + } + + for max_length in [128, 2048] { + for n in 0..100 { + let values = HashSet::from_iter(generate_uuids(n, max_length)); + let words = encode(Op::Insert, values.clone()); + assert_eq!(words.len(), compute_expected_length(&values)); + let res = decode(words); + assert_eq!(values, res); + } + } + } +} diff --git a/src/findex.rs b/src/findex.rs index b39435ee..209cd349 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -7,32 +7,34 @@ use std::{ use cosmian_crypto_core::{kdf128, CsRng, Secret}; use crate::{ - error::Error, obf::MemoryEncryptionLayer, ovec::OVec, Address, Index, Stm, ADDRESS_LENGTH, - KEY_LENGTH, + encoding::Op, error::Error, obf::MemoryEncryptionLayer, ovec::OVec, Address, Index, Stm, + ADDRESS_LENGTH, KEY_LENGTH, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Op { - Insert, - Delete, -} - // Lifetime is needed to store a reference of the memory in the vectors. -pub struct Findex<'a, Value, Memory: 'a + Stm
, Word = Vec>> -where - Value: TryFrom>, +pub struct Findex< + 'a, + Value, + TryFromError: std::error::Error, + Memory: 'a + Stm
, Word = Vec>, +> where + Value: TryFrom, Error = TryFromError>, Vec: From, { el: MemoryEncryptionLayer, vectors: Mutex, OVec<'a, MemoryEncryptionLayer>>>, encode: Box) -> Vec>>, - decode: Box>) -> HashSet>, + decode: Box>) -> Result, >>::Error>>, } -impl<'a, Value, Memory: 'a + Stm
, Word = Vec>> - Findex<'a, Value, Memory> +impl< + 'a, + Value, + TryFromError: std::error::Error, + Memory: 'a + Stm
, Word = Vec>, + > Findex<'a, Value, TryFromError, Memory> where - Value: TryFrom>, + Value: TryFrom, Error = TryFromError>, Vec: From, { /// Instantiates Findex with the given seed, and memory. @@ -40,8 +42,8 @@ where seed: Secret, rng: Arc>, stm: Memory, - encode: Box) -> Vec>>, - decode: Box>) -> HashSet>, + encode: fn(Op, HashSet) -> Vec>, + decode: fn(Vec>) -> Result, >>::Error>, ) -> Self { // TODO: should the RNG be instantiated here? // Creating many instances of Findex would need more work but potentially involve less @@ -49,8 +51,8 @@ where Self { el: MemoryEncryptionLayer::new(seed, rng, stm), vectors: Mutex::new(HashMap::new()), - encode, - decode, + encode: Box::new(encode), + decode: Box::new(decode), } } @@ -141,10 +143,11 @@ impl< 'a, Keyword: Hash + PartialEq + Eq + AsRef<[u8]>, Value: Hash + PartialEq + Eq + From>, + TryFromError: std::error::Error, Memory: 'a + Stm
, Word = Vec>, - > Index<'a, Keyword, Value> for Findex<'a, Value, Memory> + > Index<'a, Keyword, Value> for Findex<'a, Value, TryFromError, Memory> where - Value: TryFrom>, + Value: TryFrom, Error = TryFromError>, Vec: From, { type Error = Error, as Stm>::Error>; @@ -159,7 +162,12 @@ where let mut bindings = HashMap::new(); for fut in futures { let (kw, vals) = fut.await?; - bindings.insert(kw, (self.decode)(vals)); + bindings.insert( + kw, + (self.decode)(vals).map_err(|e| { + Error::, Memory::Error>::Conversion(e.to_string()) + })?, + ); } Ok(bindings) } @@ -192,23 +200,45 @@ mod tests { use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; use futures::executor::block_on; - use crate::{address::Address, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH}; - - use super::Op; + use crate::{ + address::Address, encoding::Op, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH, + }; - fn encode(_op: Op, values: HashSet) -> Vec> - where - for<'z> Vec: From<&'z Value>, - { - values.into_iter().map(|v| >::from(&v)).collect() + fn dummy_encode>>(op: Op, vs: HashSet) -> Vec> { + vs.into_iter() + .map(Into::into) + .map(|bytes| { + if op == Op::Insert { + [vec![1], bytes].concat() + } else { + [vec![0], bytes].concat() + } + }) + .collect() } - fn decode>>(words: Vec>) -> HashSet { - words.into_iter().map(Value::from).collect() + fn dummy_decode< + TryFromError: std::error::Error, + Value: Hash + PartialEq + Eq + TryFrom, Error = TryFromError>, + >( + ws: Vec>, + ) -> Result, >>::Error> { + let mut res = HashSet::with_capacity(ws.len()); + for w in ws { + if !w.is_empty() { + let v = Value::try_from(w[1..].to_vec())?; + if w[0] == 1 { + res.insert(v); + } else { + res.remove(&v); + } + } + } + Ok(res) } #[test] - fn test_insert_search() { + fn test_insert_search_delete_search() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); @@ -216,8 +246,8 @@ mod tests { seed, Arc::new(Mutex::new(rng)), kv, - Box::new(encode), - Box::new(decode), + dummy_encode, + dummy_decode, ); let bindings = HashMap::<&str, HashSet>::from_iter([ ( @@ -232,5 +262,12 @@ mod tests { block_on(findex.insert(bindings.clone().into_iter())).unwrap(); let res = block_on(findex.search(bindings.keys().cloned())).unwrap(); assert_eq!(bindings, res); + + block_on(findex.delete(bindings.clone().into_iter())).unwrap(); + let res = block_on(findex.search(bindings.keys().cloned())).unwrap(); + assert_eq!( + HashMap::from_iter([("cat", HashSet::new()), ("dog", HashSet::new())]), + res + ); } } diff --git a/src/lib.rs b/src/lib.rs index c7bb18d5..5892a8d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::type_complexity)] mod address; +mod encoding; mod error; mod findex; mod kv; From 2f31f1e097d4d40403edebe5020298633108d226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 1 Jul 2024 14:25:38 +0200 Subject: [PATCH 10/42] add search bench --- Cargo.toml | 8 +++ benches/benches.rs | 94 +++++++++++++++++++++++++++++++++++ src/encoding.rs | 121 ++++++++++++++++++++++++++++++--------------- src/findex.rs | 52 +++++-------------- src/lib.rs | 3 ++ src/obf.rs | 6 +-- 6 files changed, 200 insertions(+), 84 deletions(-) create mode 100644 benches/benches.rs diff --git a/Cargo.toml b/Cargo.toml index a8f1136a..3ac46d91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "findex-bis" version = "0.1.0" edition = "2021" +[features] +bench = [] + [dependencies] aes = "0.8.4" cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3"]} @@ -10,3 +13,8 @@ cosmian_crypto_core = {version = "9.4.0", default-features = false, features = [ [dev-dependencies] futures = "0.3.30" cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3" , "ser"]} +criterion = "0.5.1" + +[[bench]] +name = "benches" +harness = false diff --git a/benches/benches.rs b/benches/benches.rs new file mode 100644 index 00000000..e5344bb6 --- /dev/null +++ b/benches/benches.rs @@ -0,0 +1,94 @@ +#![allow(dead_code, unused)] +use std::{ + collections::{HashMap, HashSet}, + sync::{Arc, Mutex}, +}; + +use cosmian_crypto_core::{ + reexport::rand_core::{CryptoRngCore, SeedableRng}, + CsRng, Secret, +}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use findex_bis::{dummy_decode, dummy_encode, Findex, Index, KvStore}; +use futures::executor::block_on; + +fn bench_search(c: &mut Criterion) { + fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { + (0..6) + .map(|i| { + let kw = rng.next_u64().to_be_bytes(); + let vals = (1..10_i64.pow(i) as usize) + .map(|_| rng.next_u64().to_be_bytes()) + .collect::>(); + (kw, vals) + }) + .collect() + } + + let mut rng = CsRng::from_entropy(); + let index = build_benchmarking_index(&mut rng); + let seed = Secret::random(&mut rng); + let stm = KvStore::default(); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + stm, + dummy_encode, + dummy_decode, + ); + findex.insert(index.clone().into_iter()); + + { + let mut group = c.benchmark_group("Multiple bindings search"); + for (i, (kw, _)) in index.clone().into_iter().enumerate() { + let n = 10i32.pow(i as u32) as usize; + group.bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter(|| { + block_on(findex.search([kw].into_iter())).expect("search failed"); + }); + }); + } + } + + { + let mut group = c.benchmark_group("Multiple keywords search (1 binding)"); + for i in 0..4 { + let n = 10i32.pow(i) as usize; + let kws = vec![index[0].0; n]; + group.bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || kws.clone(), + |kws| { + block_on(findex.search(kws.into_iter())).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }); + } + } + + { + let mut group = c.benchmark_group("Multiple keywords search (1000 bindings)"); + for i in 0..4 { + let n = 10i32.pow(i) as usize; + let kws = vec![index[3].0; n]; + group.bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || kws.clone(), + |kws| { + block_on(findex.search(kws.into_iter())).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }); + } + } +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(5000); + targets = bench_search, +); + +criterion_main!(benches); diff --git a/src/encoding.rs b/src/encoding.rs index badaaddf..3beef6a9 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use std::{cmp::Ordering, collections::HashSet}; +use std::{cmp::Ordering, collections::HashSet, hash::Hash}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Op { @@ -44,8 +44,41 @@ pub(crate) fn decode>>(_ws: Vec>) -> HashSet>>(op: Op, vs: HashSet) -> Vec> { + vs.into_iter() + .map(Into::into) + .map(|bytes| { + if op == Op::Insert { + [vec![1], bytes].concat() + } else { + [vec![0], bytes].concat() + } + }) + .collect() +} + +pub fn dummy_decode( + ws: Vec>, +) -> Result, TryFromError> +where + for<'z> Value: Hash + PartialEq + Eq + TryFrom<&'z [u8], Error = TryFromError>, +{ + let mut res = HashSet::with_capacity(ws.len()); + for w in ws { + if !w.is_empty() { + let v = Value::try_from(&w[1..])?; + if w[0] == 1 { + res.insert(v); + } else { + res.remove(&v); + } + } + } + Ok(res) +} + #[cfg(test)] -mod tests { +pub mod tests { use cosmian_crypto_core::{ bytes_ser_de::to_leb128_len, reexport::rand_core::{CryptoRngCore, SeedableRng}, @@ -54,46 +87,66 @@ mod tests { use super::*; - #[test] - fn test_encode_decode_uuids() { - fn random_uuid(rng: &mut impl CryptoRngCore) -> [u8; 16] { - let mut res = [0; 16]; - rng.fill_bytes(&mut res); - res - } + pub fn random_uuid(rng: &mut impl CryptoRngCore) -> [u8; 16] { + let mut res = [0; 16]; + rng.fill_bytes(&mut res); + res + } - fn generate_uuids(n: usize) -> Vec<[u8; 16]> { - let mut rng = CsRng::from_entropy(); - (0..n).map(|_| random_uuid(&mut rng)).collect() - } + fn generate_uuids(n: usize) -> Vec<[u8; 16]> { + let mut rng = CsRng::from_entropy(); + (0..n).map(|_| random_uuid(&mut rng)).collect() + } - fn compute_expected_length(values: &HashSet<[u8; 16]>) -> usize { - (values.len() as f64 / 8.0).ceil() as usize - } + fn random_value(rng: &mut impl CryptoRngCore, max_length: usize) -> Vec { + let length = rng.next_u64() as usize % max_length; + let mut res = vec![0; length]; + rng.fill_bytes(&mut res); + res + } + + fn generate_values(n: usize, max_length: usize) -> Vec> { + let mut rng = CsRng::from_entropy(); + (0..n).map(|_| random_value(&mut rng, max_length)).collect() + } + fn test_encode_decode_uuids( + encode: fn(Op, HashSet<[u8; 16]>) -> Vec>, + decode: fn(Vec>) -> Result, TryFromError>, + check_len: fn(&HashSet<[u8; 16]>) -> usize, + ) { for n in 0..100 { let values = HashSet::from_iter(generate_uuids(n)); let words = encode(Op::Insert, values.clone()); - assert_eq!(words.len(), compute_expected_length(&values)); - let res = decode(words); + assert_eq!(words.len(), check_len(&values)); + let res = decode(words).unwrap(); assert_eq!(values, res); } } - #[test] - fn test_encode_decode_variable_length_values() { - fn random_value(rng: &mut impl CryptoRngCore, max_length: usize) -> Vec { - let length = rng.next_u64() as usize % max_length; - let mut res = vec![0; length]; - rng.fill_bytes(&mut res); - res + fn test_encode_decode_variable_length_values( + encode: fn(Op, HashSet>) -> Vec>, + decode: fn(Vec>) -> Result>, TryFromError>, + check_len: fn(&HashSet>) -> usize, + ) { + for max_length in [128, 2048] { + for n in 0..100 { + let values = HashSet::from_iter(generate_values(n, max_length)); + let words = encode(Op::Insert, values.clone()); + assert_eq!(words.len(), check_len(&values)); + let res = decode(words).unwrap(); + assert_eq!(values, res); + } } + } - fn generate_uuids(n: usize, max_length: usize) -> Vec> { - let mut rng = CsRng::from_entropy(); - (0..n).map(|_| random_value(&mut rng, max_length)).collect() - } + #[test] + fn test_dummy_encoding() { + test_encode_decode_uuids(dummy_encode, dummy_decode, |h| h.len()); + test_encode_decode_variable_length_values(dummy_encode, dummy_decode, |h| h.len()); + } + fn test_encodings() { fn compute_expected_length(values: &HashSet>) -> usize { let total_length = values .iter() @@ -102,15 +155,5 @@ mod tests { .sum::(); (total_length as f64 / (8 * 16) as f64).ceil() as usize } - - for max_length in [128, 2048] { - for n in 0..100 { - let values = HashSet::from_iter(generate_uuids(n, max_length)); - let words = encode(Op::Insert, values.clone()); - assert_eq!(words.len(), compute_expected_length(&values)); - let res = decode(words); - assert_eq!(values, res); - } - } } } diff --git a/src/findex.rs b/src/findex.rs index 209cd349..b9b92bfd 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -18,13 +18,14 @@ pub struct Findex< TryFromError: std::error::Error, Memory: 'a + Stm
, Word = Vec>, > where - Value: TryFrom, Error = TryFromError>, + // values are serializable (but do not depend on `serde`) + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, Vec: From, { el: MemoryEncryptionLayer, vectors: Mutex, OVec<'a, MemoryEncryptionLayer>>>, encode: Box) -> Vec>>, - decode: Box>) -> Result, >>::Error>>, + decode: Box>) -> Result, TryFromError>>, } impl< @@ -34,7 +35,7 @@ impl< Memory: 'a + Stm
, Word = Vec>, > Findex<'a, Value, TryFromError, Memory> where - Value: TryFrom, Error = TryFromError>, + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, Vec: From, { /// Instantiates Findex with the given seed, and memory. @@ -43,7 +44,7 @@ where rng: Arc>, stm: Memory, encode: fn(Op, HashSet) -> Vec>, - decode: fn(Vec>) -> Result, >>::Error>, + decode: fn(Vec>) -> Result, TryFromError>, ) -> Self { // TODO: should the RNG be instantiated here? // Creating many instances of Findex would need more work but potentially involve less @@ -142,12 +143,12 @@ where impl< 'a, Keyword: Hash + PartialEq + Eq + AsRef<[u8]>, - Value: Hash + PartialEq + Eq + From>, + Value: Hash + PartialEq + Eq, TryFromError: std::error::Error, Memory: 'a + Stm
, Word = Vec>, > Index<'a, Keyword, Value> for Findex<'a, Value, TryFromError, Memory> where - Value: TryFrom, Error = TryFromError>, + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, Vec: From, { type Error = Error, as Stm>::Error>; @@ -193,7 +194,6 @@ where mod tests { use std::{ collections::{HashMap, HashSet}, - hash::Hash, sync::{Arc, Mutex}, }; @@ -201,42 +201,12 @@ mod tests { use futures::executor::block_on; use crate::{ - address::Address, encoding::Op, kv::KvStore, Findex, Index, Value, ADDRESS_LENGTH, + address::Address, + encoding::{dummy_decode, dummy_encode}, + kv::KvStore, + Findex, Index, Value, ADDRESS_LENGTH, }; - fn dummy_encode>>(op: Op, vs: HashSet) -> Vec> { - vs.into_iter() - .map(Into::into) - .map(|bytes| { - if op == Op::Insert { - [vec![1], bytes].concat() - } else { - [vec![0], bytes].concat() - } - }) - .collect() - } - - fn dummy_decode< - TryFromError: std::error::Error, - Value: Hash + PartialEq + Eq + TryFrom, Error = TryFromError>, - >( - ws: Vec>, - ) -> Result, >>::Error> { - let mut res = HashSet::with_capacity(ws.len()); - for w in ws { - if !w.is_empty() { - let v = Value::try_from(w[1..].to_vec())?; - if w[0] == 1 { - res.insert(v); - } else { - res.remove(&v); - } - } - } - Ok(res) - } - #[test] fn test_insert_search_delete_search() { let mut rng = CsRng::from_entropy(); diff --git a/src/lib.rs b/src/lib.rs index 5892a8d7..60574a9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,9 @@ pub use kv::KvStore; pub use stm::Stm; pub use value::Value; +#[cfg(feature = "bench")] +pub use encoding::{dummy_decode, dummy_encode}; + pub const ADDRESS_LENGTH: usize = 16; pub const KEY_LENGTH: usize = 32; diff --git a/src/obf.rs b/src/obf.rs index afe219f9..988916fe 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -121,10 +121,8 @@ impl, Word = Vec>> MemoryEncry } } -impl< - Memory: Stm
, Word = Vec>, // use a `Vec` because `const` generics - // are not allowed in `const` operations - > Stm for MemoryEncryptionLayer +impl, Word = Vec>> Stm + for MemoryEncryptionLayer { type Address = Address; From 6301d8faaba036fcb20963a306372bf675efd41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 1 Jul 2024 17:09:40 +0200 Subject: [PATCH 11/42] fix benches and limit cache to "interesting" values Only values that are searched once are then cached. This limits the size of the cache to the size of the Entry Table (worst case). --- benches/benches.rs | 69 ++++++++++++++++++++-------- src/address.rs | 1 - src/error.rs | 1 + src/obf.rs | 111 +++++++++++++++++++++++---------------------- src/ovec.rs | 5 +- 5 files changed, 109 insertions(+), 78 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index e5344bb6..3c48e3ef 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -12,19 +12,19 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use findex_bis::{dummy_decode, dummy_encode, Findex, Index, KvStore}; use futures::executor::block_on; -fn bench_search(c: &mut Criterion) { - fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { - (0..6) - .map(|i| { - let kw = rng.next_u64().to_be_bytes(); - let vals = (1..10_i64.pow(i) as usize) - .map(|_| rng.next_u64().to_be_bytes()) - .collect::>(); - (kw, vals) - }) - .collect() - } +fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { + (0..4) + .map(|i| { + let kw = rng.next_u64().to_be_bytes(); + let vals = (0..10_i64.pow(i) as usize) + .map(|_| rng.next_u64().to_be_bytes()) + .collect::>(); + (kw, vals) + }) + .collect() +} +fn bench_search(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let index = build_benchmarking_index(&mut rng); let seed = Secret::random(&mut rng); @@ -36,15 +36,15 @@ fn bench_search(c: &mut Criterion) { dummy_encode, dummy_decode, ); - findex.insert(index.clone().into_iter()); + block_on(findex.insert(index.clone().into_iter())).unwrap(); { let mut group = c.benchmark_group("Multiple bindings search"); - for (i, (kw, _)) in index.clone().into_iter().enumerate() { + for (i, (kw, vals)) in index.clone().into_iter().enumerate() { let n = 10i32.pow(i as u32) as usize; group.bench_function(BenchmarkId::from_parameter(n), |b| { b.iter(|| { - block_on(findex.search([kw].into_iter())).expect("search failed"); + let res = block_on(findex.search([kw].into_iter())).expect("search failed"); }); }); } @@ -52,7 +52,7 @@ fn bench_search(c: &mut Criterion) { { let mut group = c.benchmark_group("Multiple keywords search (1 binding)"); - for i in 0..4 { + for i in 0..=4 { let n = 10i32.pow(i) as usize; let kws = vec![index[0].0; n]; group.bench_function(BenchmarkId::from_parameter(n), |b| { @@ -69,7 +69,7 @@ fn bench_search(c: &mut Criterion) { { let mut group = c.benchmark_group("Multiple keywords search (1000 bindings)"); - for i in 0..4 { + for i in 0..=4 { let n = 10i32.pow(i) as usize; let kws = vec![index[3].0; n]; group.bench_function(BenchmarkId::from_parameter(n), |b| { @@ -85,10 +85,43 @@ fn bench_search(c: &mut Criterion) { } } +fn bench_insert(c: &mut Criterion) { + let mut rng = CsRng::from_entropy(); + let index = build_benchmarking_index(&mut rng); + let seed = Secret::random(&mut rng); + { + let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); + for (i, (kw, vals)) in index.clone().into_iter().enumerate() { + let n = 10i32.pow(i as u32) as usize; + group.bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || { + let rng = CsRng::from_entropy(); + let seed = seed.clone(); + let vals = vals.clone(); + (seed, rng, kw, vals) + }, + |(seed, rng, kw, vals)| { + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + KvStore::default(), + dummy_encode, + dummy_decode, + ); + block_on(findex.insert([(kw, vals)].into_iter())).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }); + } + } +} + criterion_group!( name = benches; config = Criterion::default().sample_size(5000); - targets = bench_search, + targets = bench_search, bench_insert, ); criterion_main!(benches); diff --git a/src/address.rs b/src/address.rs index c1f93d0a..8385ec30 100644 --- a/src/address.rs +++ b/src/address.rs @@ -53,7 +53,6 @@ impl Add for Address { pos += 1; if (pos % LENGTH) == 0 { - println!("circling"); carry -= 1; } } diff --git a/src/error.rs b/src/error.rs index 70d97c87..838cc410 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,7 @@ pub enum Error { Memory(MemoryError), Conversion(String), MissingValue(Address), + EncryptionLayer(String), } impl Display for Error { diff --git a/src/obf.rs b/src/obf.rs index 988916fe..f9edb16f 100644 --- a/src/obf.rs +++ b/src/obf.rs @@ -6,7 +6,7 @@ use std::{ use crate::{address::Address, error::Error, Stm, ADDRESS_LENGTH, KEY_LENGTH}; use aes::{ - cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, KeyInit}, + cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, Aes256, }; use cosmian_crypto_core::{ @@ -18,7 +18,7 @@ use cosmian_crypto_core::{ pub struct MemoryEncryptionLayer, Word = Vec>> { permutation_key: SymmetricKey, encryption_key: SymmetricKey<32>, - cache: Arc, Vec), Vec>>>, + cache: Arc, HashMap, Vec>>>>, rng: Arc>, stm: Memory, } @@ -45,7 +45,7 @@ impl, Word = Vec>> MemoryEncry } /// Retains values cached for the given keys only. - pub fn retain_cached_keys(&self, keys: &HashSet<(Memory::Address, Vec)>) { + pub fn retain_cached_keys(&self, keys: &HashSet) { self.cache .lock() .expect("poisoned mutex") @@ -53,71 +53,81 @@ impl, Word = Vec>> MemoryEncry .retain(|k, _| keys.contains(k)); } - /// Gets the encrypted value from the cache and computes it upon cache miss. - pub fn encrypt( + /// Decrypts the given value and caches the ciphertext. + fn find_or_encrypt( &self, ptx: &[u8], tok: &Memory::Address, ) -> Result, Error> { - let k = (tok.clone(), ptx.to_vec()); - if let Some(ctx) = self.find(&k) { + if let Some(ctx) = self.find(tok, ptx) { Ok(ctx) } else { - let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); - let ctx = Aes256Gcm::new(&self.encryption_key) - .encrypt(&nonce, ptx, Some(tok)) - .map_err(Error::Encryption)?; - let ctx = [nonce.as_bytes(), &ctx].concat(); - self.bind(k, ctx.clone()); + let ctx = self.encrypt(ptx, tok)?; + self.bind(tok.clone(), ptx.to_vec(), ctx.clone()); Ok(ctx) } } /// Decrypts the given value and caches the ciphertext. - pub fn decrypt( + fn bind_and_decrypt( &self, ctx: Vec, tok: Memory::Address, + ) -> Result, Error> { + let ptx = self.decrypt(&ctx, &tok)?; + self.bind(tok, ptx.clone(), ctx); + Ok(ptx) + } + + fn encrypt( + &self, + ptx: &[u8], + tok: &Memory::Address, + ) -> Result, Error> { + let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); + let ctx = Aes256Gcm::new(&self.encryption_key) + .encrypt(&nonce, ptx, Some(tok)) + .map_err(Error::Encryption)?; + Ok([nonce.as_bytes(), &ctx].concat()) + } + + #[inline] + fn decrypt( + &self, + ctx: &[u8], + tok: &Memory::Address, ) -> Result, Error> { if ctx.len() < Aes256Gcm::NONCE_LENGTH { return Err(Error::Parsing("ciphertext too small".to_string())); } let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) .map_err(|e| Error::Parsing(e.to_string()))?; - let ptx = Aes256Gcm::new(&self.encryption_key) - .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(&tok)) - .map_err(Error::Encryption)?; - self.bind((tok, ptx.clone()), ctx); - Ok(ptx) + Aes256Gcm::new(&self.encryption_key) + .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(tok)) + .map_err(Error::Encryption) } - pub fn reorder(&self, mut a: Memory::Address) -> Memory::Address { - Aes256::new(GenericArray::from_slice(&self.permutation_key)) - .decrypt_block(GenericArray::from_mut_slice(&mut a)); - a - } - - pub fn permute(&self, mut a: Memory::Address) -> Memory::Address { + fn permute(&self, mut a: Memory::Address) -> Memory::Address { Aes256::new(GenericArray::from_slice(&self.permutation_key)) .encrypt_block(GenericArray::from_mut_slice(&mut a)); a } - pub fn bind(&self, k: (Memory::Address, Vec), v: Vec) { - self.cache - .lock() - .expect("poisoned lock") - .deref_mut() - .insert(k, v); + fn bind(&self, tok: Memory::Address, ptx: Vec, ctx: Vec) { + let mut cache = self.cache.lock().expect("poisoned lock"); + if let Some(bindings) = cache.get_mut(&tok) { + bindings.insert(ptx, ctx); + } } - pub fn find(&self, k: &(Address, Vec)) -> Option> { - self.cache - .lock() - .expect("poisoned lock") - .deref_mut() - .get(k) - .cloned() + fn find(&self, tok: &Address, ref_ptx: &[u8]) -> Option> { + let mut cache = self.cache.lock().expect("poisoned lock"); + if let Some(bindings) = cache.get(tok) { + bindings.get(ref_ptx).cloned() + } else { + cache.insert(tok.clone(), HashMap::new()); + None + } } } @@ -139,7 +149,7 @@ impl, Word = Vec>> Stm bindings .into_iter() .zip(tokens) - .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(ctx, tok.clone())).transpose()) + .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(&ctx, &tok)).transpose()) .collect() } @@ -148,26 +158,17 @@ impl, Word = Vec>> Stm guard: (Self::Address, Option), bindings: Vec<(Self::Address, Self::Word)>, ) -> Result, Self::Error> { - let (a, v) = guard; - let tok = self.permute(a); - let old = v.and_then(|v| self.find(&(tok.clone(), v))); - + let tok = self.permute(guard.0.clone()); + let old = guard.1.and_then(|ptx| self.find(&tok, &ptx)); let bindings = bindings .into_iter() .map(|(a, v)| { let tok = self.permute(a); - self.encrypt(&v, &tok).map(|ctx| (tok, ctx)) + self.find_or_encrypt(&v, &tok).map(|ctx| (tok, ctx)) }) .collect::, _>>()?; - - let ctx = self.stm.guarded_write((tok.clone(), old), bindings).await?; - - if let Some(ctx) = ctx { - let ptx = self.decrypt(ctx.clone(), tok)?; - Ok(Some(ptx)) - } else { - Ok(None) - } + let cur = self.stm.guarded_write((tok.clone(), old), bindings).await?; + cur.map(|ctx| self.bind_and_decrypt(ctx, tok)).transpose() } } @@ -194,7 +195,7 @@ mod tests { let tok = Address::::random(&mut rng); let ptx = vec![1]; let ctx = obf.encrypt(&ptx, &tok).unwrap(); - let res = obf.decrypt(ctx, tok).unwrap(); + let res = obf.decrypt(&ctx, &tok).unwrap(); assert_eq!(ptx.len(), res.len()); assert_eq!(ptx, res); } diff --git a/src/ovec.rs b/src/ovec.rs index 9dcaa4b5..f9956e36 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -142,10 +142,7 @@ impl< let cur_header = res[0] .clone() - .map(|v| { - println!("{v:?}"); - Header::try_from(v.as_slice()) - }) + .map(|v| Header::try_from(v.as_slice())) .transpose() .map_err(Error::Conversion)? .unwrap_or_default(); From a87bd30bfbf0dcb0b7441ec8b347fe309ce00144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 5 Jul 2024 12:06:04 +0200 Subject: [PATCH 12/42] fix some gnarly concurrency bug and improve benches --- .github/workflows/benches.yml | 2 +- Cargo.toml | 10 +- benches/benches.rs | 205 +++++++++++++++------ examples/insert.rs | 38 ++++ src/adt.rs | 131 ++++++++++++++ src/el.rs | 326 ++++++++++++++++++++++++++++++++++ src/encoding.rs | 195 +++++++++++--------- src/error.rs | 6 +- src/findex.rs | 141 ++++++++------- src/kv.rs | 28 ++- src/lib.rs | 39 +--- src/obf.rs | 277 ----------------------------- src/ovec.rs | 238 ++++++++++++------------- src/stm.rs | 27 --- src/value.rs | 6 + 15 files changed, 999 insertions(+), 670 deletions(-) create mode 100644 examples/insert.rs create mode 100644 src/adt.rs create mode 100644 src/el.rs delete mode 100644 src/obf.rs delete mode 100644 src/stm.rs diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index dcbf692f..ccd97357 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -1,7 +1,7 @@ --- name: Benches -on: [workflow_dispatch, pull_request] +on: [workflow_dispatch, pull_request, push] jobs: bench: diff --git a/Cargo.toml b/Cargo.toml index 3ac46d91..8fa5466b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,19 @@ bench = [] [dependencies] aes = "0.8.4" -cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3"]} +cosmian_crypto_core = {git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/derive-clone-for-symkey", default-features = false, features = ["aes", "sha3"]} [dev-dependencies] futures = "0.3.30" -cosmian_crypto_core = {version = "9.4.0", default-features = false, features = ["aes", "sha3" , "ser"]} +cosmian_crypto_core = {git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/derive-clone-for-symkey", default-features = false, features = ["aes", "sha3"]} criterion = "0.5.1" +tokio = {version = "1.38.0", features = ["rt", "macros", "rt-multi-thread", "time"]} [[bench]] name = "benches" harness = false +required-features = ["bench"] + +[[example]] +name = "insert" +required-features = ["bench"] diff --git a/benches/benches.rs b/benches/benches.rs index 3c48e3ef..4511a847 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex}, + time::Duration, }; use cosmian_crypto_core::{ @@ -9,10 +10,13 @@ use cosmian_crypto_core::{ CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex_bis::{dummy_decode, dummy_encode, Findex, Index, KvStore}; +use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; use futures::executor::block_on; -fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { +/// Builds an index that associates each `kw_i` to `10^i` values, both random 64-bit values. +fn build_benchmarking_bindings_index( + rng: &mut impl CryptoRngCore, +) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { (0..4) .map(|i| { let kw = rng.next_u64().to_be_bytes(); @@ -24,59 +28,90 @@ fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashS .collect() } +/// Builds an index that associates 10^3 `kw_i` to a single value, both random 64-bit values. +fn build_benchmarking_keywords_index( + rng: &mut impl CryptoRngCore, +) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { + (0..10usize.pow(3)) + .map(|_| { + let kw = rng.next_u64().to_be_bytes(); + let val = rng.next_u64().to_be_bytes(); + (kw, HashSet::from([val])) + }) + .collect() +} + fn bench_search(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); - let index = build_benchmarking_index(&mut rng); let seed = Secret::random(&mut rng); - let stm = KvStore::default(); - let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng)), - stm, - dummy_encode, - dummy_decode, - ); - block_on(findex.insert(index.clone().into_iter())).unwrap(); - + // Bench the impact of the binding multiplicity. { - let mut group = c.benchmark_group("Multiple bindings search"); + let stm = KvStore::default(); + let index = build_benchmarking_bindings_index(&mut rng); + let findex = Findex::new( + seed.clone(), + Arc::new(Mutex::new(rng.clone())), + stm, + dummy_encode::<16, _>, + dummy_decode, + ); + block_on(findex.insert(index.clone().into_iter())).unwrap(); + + let mut group = c.benchmark_group("Multiple bindings search (1 keyword)"); for (i, (kw, vals)) in index.clone().into_iter().enumerate() { let n = 10i32.pow(i as u32) as usize; group.bench_function(BenchmarkId::from_parameter(n), |b| { - b.iter(|| { - let res = block_on(findex.search([kw].into_iter())).expect("search failed"); - }); + b.iter_batched( + || [kw].into_iter(), + |kws| { + block_on(findex.search(kws)).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); }); } } + // Bench the impact of the keyword multiplicity. { + let stm = KvStore::default(); + let index = build_benchmarking_keywords_index(&mut rng); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + stm.clone(), + dummy_encode::<16, _>, + dummy_decode, + ); + block_on(findex.insert(index.clone().into_iter())).unwrap(); let mut group = c.benchmark_group("Multiple keywords search (1 binding)"); - for i in 0..=4 { + for i in 0..4 { let n = 10i32.pow(i) as usize; - let kws = vec![index[0].0; n]; - group.bench_function(BenchmarkId::from_parameter(n), |b| { + group.bench_function(format!("reading {n} words from memory"), |b| { + // Attempts to bench all external costs (somehow, cloning the keywords impacts the + // benches). b.iter_batched( - || kws.clone(), - |kws| { - block_on(findex.search(kws.into_iter())).expect("search failed"); + || { + stm.clone() + .into_iter() + .map(|(a, w)| a) + .take(n) + .collect::>() }, + |addresses| block_on(stm.batch_read(addresses)).expect("batch read failed"), criterion::BatchSize::SmallInput, ); }); - } - } - - { - let mut group = c.benchmark_group("Multiple keywords search (1000 bindings)"); - for i in 0..=4 { - let n = 10i32.pow(i) as usize; - let kws = vec![index[3].0; n]; + // Go bench it. group.bench_function(BenchmarkId::from_parameter(n), |b| { b.iter_batched( - || kws.clone(), + || { + // Using .cloned() instead of .clone() reduces the overhead (maybe because it + // only clones what is needed) + index.iter().map(|(kw, val)| kw).take(n).cloned() + }, |kws| { - block_on(findex.search(kws.into_iter())).expect("search failed"); + block_on(findex.search(kws)).expect("search failed"); }, criterion::BatchSize::SmallInput, ); @@ -87,33 +122,93 @@ fn bench_search(c: &mut Criterion) { fn bench_insert(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); - let index = build_benchmarking_index(&mut rng); + let index = build_benchmarking_bindings_index(&mut rng); let seed = Secret::random(&mut rng); + + // Bench the impact of the binding multiplicity. { let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); for (i, (kw, vals)) in index.clone().into_iter().enumerate() { let n = 10i32.pow(i as u32) as usize; - group.bench_function(BenchmarkId::from_parameter(n), |b| { - b.iter_batched( - || { - let rng = CsRng::from_entropy(); - let seed = seed.clone(); - let vals = vals.clone(); - (seed, rng, kw, vals) - }, - |(seed, rng, kw, vals)| { - let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng)), - KvStore::default(), - dummy_encode, - dummy_decode, - ); - block_on(findex.insert([(kw, vals)].into_iter())).expect("search failed"); - }, - criterion::BatchSize::SmallInput, - ); - }); + group + .bench_function(format!("inserting {n} words to memory"), |b| { + b.iter_batched( + || { + let rng = CsRng::from_entropy(); + let seed = seed.clone(); + let vals = vals.clone(); + let stm = KvStore::default(); + let words = dummy_encode::<16, _>(Op::Insert, vals).unwrap(); + let bindings = words + .into_iter() + .enumerate() + .map(|(i, w)| ([i; 16], w)) + .collect::>(); + (stm, bindings) + }, + |(stm, bindings)| { + block_on(stm.guarded_write(([0; 16], None), bindings)) + .expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + group + .bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || { + let seed = seed.clone(); + let vals = vals.clone(); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng.clone())), + KvStore::default(), + dummy_encode::<16, _>, + dummy_decode, + ); + let bindings = [(kw, vals)].into_iter(); + (findex, bindings) + }, + |(findex, bindings)| { + block_on(findex.insert(bindings)).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + } + } + + // Bench the impact of the keyword multiplicity. + { + let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); + for (i, (kw, vals)) in index.clone().into_iter().enumerate() { + let n = 10i32.pow(i as u32) as usize; + group + .bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || { + let rng = CsRng::from_entropy(); + let seed = seed.clone(); + let vals = vals.clone(); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + KvStore::default(), + dummy_encode::<16, _>, + dummy_decode, + ); + let bindings = [(kw, vals)].into_iter(); + (findex, bindings) + }, + |(findex, bindings)| { + block_on(findex.insert(bindings)).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); } } } diff --git a/examples/insert.rs b/examples/insert.rs new file mode 100644 index 00000000..21882d1e --- /dev/null +++ b/examples/insert.rs @@ -0,0 +1,38 @@ +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; + +use cosmian_crypto_core::{ + reexport::rand_core::{CryptoRngCore, SeedableRng}, + CsRng, Secret, +}; +use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore}; +use futures::executor::block_on; + +fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { + (0..6) + .map(|i| { + let kw = rng.next_u64().to_be_bytes(); + let vals = (0..10_i64.pow(i) as usize) + .map(|_| rng.next_u64().to_be_bytes()) + .collect::>(); + (kw, vals) + }) + .collect() +} + +fn main() { + let mut rng = CsRng::from_entropy(); + let index = build_benchmarking_index(&mut rng); + let seed = Secret::random(&mut rng); + let findex = Findex::new( + seed, + Arc::new(Mutex::new(rng)), + KvStore::default(), + dummy_encode::<16, _>, + dummy_decode, + ); + block_on(findex.insert(index.clone().into_iter())).expect("insert failed"); + block_on(findex.search(vec![index[5].0; 1000].into_iter())).expect("search failed"); +} diff --git a/src/adt.rs b/src/adt.rs new file mode 100644 index 00000000..d808f7b8 --- /dev/null +++ b/src/adt.rs @@ -0,0 +1,131 @@ +use std::{ + collections::{HashMap, HashSet}, + future::Future, + hash::Hash, +}; + +/// An index stores *bindings*, that associate a keyword with a value. All values bound to the same +/// keyword are said to be *indexed under* this keyword. The number of such values is called the +/// volume of a keyword. +pub trait IndexADT { + type Error: Send + Sync + std::error::Error; + + /// Search the index for the values bound to the given keywords. + fn search( + &self, + keywords: impl Iterator, + ) -> impl Future>, Self::Error>>; + + /// Adds the given bindings to the index. + fn insert( + &self, + bindings: impl Sync + Send + Iterator)>, + ) -> impl Send + Sync + Future>; + + /// Removes the given bindings from the index. + fn delete( + &self, + bindings: impl Sync + Send + Iterator)>, + ) -> impl Send + Sync + Future>; +} + +pub trait VectorADT: Send + Sync { + /// Vectors are homogeneous. + type Value: Send + Sync; + + /// Vector error. + type Error: Send + Sync + std::error::Error; + + /// Pushes the given values at the end of this vector. + fn push( + &mut self, + vs: Vec, + ) -> impl Send + Sync + Future>; + + /// Reads all values stored in this vector. + fn read(&self) -> impl Send + Sync + Future, Self::Error>>; +} + +/// A Software Transactional Memory: all operations exposed are atomic. +pub trait MemoryADT { + /// Address space. + type Address; + + /// Word space. + type Word; + + /// Memory error. + type Error: Send + Sync + std::error::Error; + + /// Reads the words from the given addresses. + fn batch_read( + &self, + a: Vec, + ) -> impl Send + Sync + Future>, Self::Error>>; + + /// Write the given words at the given addresses if the word currently stored at the guard + /// address is the one given, and returns this guard word. + fn guarded_write( + &self, + guard: (Self::Address, Option), + tasks: Vec<(Self::Address, Self::Word)>, + ) -> impl Send + Sync + Future, Self::Error>>; +} + +#[cfg(test)] +pub(crate) mod tests { + + use futures::{executor::block_on, future::join_all}; + + use super::*; + + /// Adding information from different copies of the same vector should be visible by all + /// copies. + pub async fn test_vector_sequential( + v: &(impl Clone + VectorADT), + ) { + let mut v1 = v.clone(); + let mut v2 = v.clone(); + let values = (0..10).map(|n| [n; LENGTH]).collect::>(); + v1.push(values[..5].to_vec()).await.unwrap(); + v2.push(values[..5].to_vec()).await.unwrap(); + v1.push(values[5..].to_vec()).await.unwrap(); + v2.push(values[5..].to_vec()).await.unwrap(); + assert_eq!( + [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), + v.read().await.unwrap() + ); + } + + pub async fn test_vector_concurrent< + const LENGTH: usize, + V: 'static + Clone + VectorADT, + >( + v: &V, + ) { + let n = 100; + let m = 2; + let values = (0..n * m).map(|i| [i as u8; LENGTH]).collect::>(); + let handles = values + .chunks_exact(m) + .map(|vals| { + let vals = vals.to_vec(); + let mut vec = v.clone(); + tokio::spawn(async move { + for val in vals { + vec.push(vec![val]).await.unwrap(); + } + }) + }) + .collect::>(); + for h in join_all(handles).await { + h.unwrap(); + } + let mut res = block_on(v.read()).unwrap(); + let old = res.clone(); + res.sort(); + assert_ne!(old, res); + assert_eq!(res.len(), n * m); + assert_eq!(res, values); + } +} diff --git a/src/el.rs b/src/el.rs new file mode 100644 index 00000000..90b61faa --- /dev/null +++ b/src/el.rs @@ -0,0 +1,326 @@ +use std::{ + collections::{HashMap, HashSet}, + ops::DerefMut, + sync::{Arc, Mutex, MutexGuard}, +}; + +use crate::{address::Address, error::Error, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH}; +use aes::{ + cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, + Aes256, +}; +use cosmian_crypto_core::{ + Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, Secret, + SymmetricKey, +}; + +/// The encryption layers is built on top of an encrypted memory implementing the `MemoryADT` and +/// exposes a plaintext virtual memory interface implementing the `MemoryADT`. +/// +/// This type is thread-safe. +#[derive(Debug, Clone)] +pub struct MemoryEncryptionLayer< + const WORD_LENGTH: usize, + Memory: MemoryADT
>, +> { + k_p: SymmetricKey, + k_e: SymmetricKey, + cch: Arc, HashMap<[u8; WORD_LENGTH], Memory::Word>>>>, + rng: Arc>, + mem: Memory, +} + +impl< + const WORD_LENGTH: usize, + Memory: Send + Sync + MemoryADT
, Word = Vec>, + > MemoryEncryptionLayer +{ + /// Instantiates a new memory encryption layer. + pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { + let k_p = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); + let k_e = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); + let cch = Arc::new(Mutex::new(HashMap::new())); + Self { + k_p, + k_e, + cch, + rng, + mem: stm, + } + } + + #[inline(always)] + fn rng(&self) -> MutexGuard { + self.rng.lock().expect("poisoned lock") + } + + /// Retains values cached for the given keys only. + pub fn retain_cached_keys(&self, keys: &HashSet) { + self.cch + .lock() + .expect("poisoned mutex") + .deref_mut() + .retain(|k, _| keys.contains(k)); + } + + /// Decrypts the given value and caches the ciphertext. + fn find_or_encrypt( + &self, + ptx: &[u8; WORD_LENGTH], + tok: &Memory::Address, + ) -> Result, Error> { + let mut cache = self.cch.lock().expect("poisoned lock"); + if let Some(bindings) = cache.get_mut(tok) { + let ctx = bindings.get(ptx).cloned(); + if let Some(ctx) = ctx { + Ok(ctx) + } else { + let ctx = self.encrypt(ptx, tok)?; + bindings.insert(*ptx, ctx.clone()); + Ok(ctx) + } + } else { + // token is not marked + drop(cache); + self.encrypt(ptx, tok) + } + } + + /// Decrypts the given value and caches the ciphertext. + fn decrypt_and_bind( + &self, + ctx: Vec, + tok: &Memory::Address, + ) -> Result<[u8; WORD_LENGTH], Error> { + let ptx = self.decrypt(&ctx, tok)?; + self.bind(tok, ptx, ctx); + Ok(ptx) + } + + /// Encrypts this plaintext under this associated data + fn encrypt( + &self, + ptx: &[u8], + ad: &[u8], + ) -> Result, Error> { + let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); + let ctx = Aes256Gcm::new(&self.k_e) + .encrypt(&nonce, ptx, Some(ad)) + .map_err(Error::Crypto)?; + Ok([nonce.as_bytes(), &ctx].concat()) + } + + /// Decrypts this ciphertext under this associated data. + fn decrypt( + &self, + ctx: &[u8], + ad: &[u8], + ) -> Result<[u8; WORD_LENGTH], Error> { + if ctx.len() < Aes256Gcm::NONCE_LENGTH { + return Err(Error::Parsing("ciphertext too small".to_string())); + } + let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) + .map_err(|e| Error::Parsing(e.to_string()))?; + let ptx = Aes256Gcm::new(&self.k_e) + .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(ad)) + .map_err(Error::Crypto)?; + <[u8; WORD_LENGTH]>::try_from(ptx.as_slice()).map_err(|e| Error::Conversion(e.to_string())) + } + + /// Permutes the given memory address. + fn permute(&self, mut a: Memory::Address) -> Memory::Address { + Aes256::new(GenericArray::from_slice(&self.k_p)) + .encrypt_block(GenericArray::from_mut_slice(&mut a)); + a + } + + /// Binds the given (tok, ptx, ctx) triple iff this token is marked. + fn bind(&self, tok: &Memory::Address, ptx: [u8; WORD_LENGTH], ctx: Memory::Word) { + let mut cache = self.cch.lock().expect("poisoned lock"); + if let Some(bindings) = cache.get_mut(tok) { + bindings.insert(ptx, ctx); + } + } + + /// Retrieves the ciphertext bound to the given (tok, ptx) couple. + /// Marks the token for later binding if it does not belong to any binding. + fn find_or_mark( + &self, + tok: &Memory::Address, + ptx: &Option<[u8; WORD_LENGTH]>, + ) -> Result, ::Error> { + let mut cache = self.cch.lock().expect("poisoned lock"); + if let Some(ptx) = ptx { + if let Some(bindings) = cache.get(tok) { + // This token is marked. + if let Some(ctx) = bindings.get(ptx) { + return Ok(Some(ctx.clone())); + } + return Err(Error::CorruptedMemoryCache); + } + } + // marking a token consists in binding it to an empty map that can later be used to + // store ctx/ptx bindings. + cache.entry(tok.clone()).or_default(); + Ok(None) + } +} + +impl< + const WORD_LENGTH: usize, + // NOTE: base-memory-word length cannot be typed since "generic parameters may not be + // used in const operations". What we would have wanted is this: + // ``` + // Memory: MemoryADT< + // Address = Address, + // Word = [u8; WORD_LENGTH + Aes256Gcm::MAC_LENGTH + Aes256Gcm::NONCE_LENGTH], + // >, + // ``` + Memory: Send + Sync + MemoryADT
, Word = Vec>, + > MemoryADT for MemoryEncryptionLayer +{ + type Address = Address; + + type Word = [u8; WORD_LENGTH]; + + type Error = Error; + + async fn batch_read( + &self, + addresses: Vec, + ) -> Result>, Self::Error> { + let tokens = addresses.into_iter().map(|a| self.permute(a)).collect(); + let bindings = self.mem.batch_read(Vec::clone(&tokens)).await?; + bindings + .into_iter() + .zip(tokens) + .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt_and_bind(ctx, &tok)).transpose()) + .collect() + } + + async fn guarded_write( + &self, + guard: (Self::Address, Option), + bindings: Vec<(Self::Address, Self::Word)>, + ) -> Result, Self::Error> { + let (a, v) = guard; + let tok = self.permute(a); + let old = self.find_or_mark(&tok, &v)?; + let bindings = bindings + .into_iter() + .map(|(a, v)| { + let tok = self.permute(a); + self.find_or_encrypt(&v, &tok).map(|ctx| (tok, ctx)) + }) + .collect::>()?; + let cur = self + .mem + .guarded_write((tok.clone(), old.clone()), bindings) + .await?; + let res = cur + .clone() + .map(|ctx| self.decrypt_and_bind(ctx, &tok)) + .transpose()?; + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + use futures::executor::block_on; + + use crate::{ + address::Address, + el::{MemoryEncryptionLayer, ADDRESS_LENGTH}, + kv::KvStore, + MemoryADT, + }; + + const WORD_LENGTH: usize = 1; + + #[test] + fn test_encrypt_decrypt() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::default(); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let tok = Address::::random(&mut rng); + let ptx = [1; WORD_LENGTH]; + let ctx = obf.encrypt(&ptx, &tok).unwrap(); + let res = obf.decrypt(&ctx, &tok).unwrap(); + assert_eq!(ptx.len(), res.len()); + assert_eq!(ptx, res); + } + + /// Ensures a transaction can express an vector push operation: + /// - the counter is correctly incremented and all values are written; + /// - using the wrong value in the guard fails the operation and returns the current value. + #[test] + fn test_vector_push() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let kv = KvStore::, Vec>::default(); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + + let header_addr = Address::::random(&mut rng); + + let val_addr_1 = Address::::random(&mut rng); + let val_addr_2 = Address::::random(&mut rng); + let val_addr_3 = Address::::random(&mut rng); + let val_addr_4 = Address::::random(&mut rng); + + assert_eq!( + block_on(obf.guarded_write( + (header_addr.clone(), None), + vec![ + (header_addr.clone(), [2]), + (val_addr_1.clone(), [1]), + (val_addr_2.clone(), [1]) + ] + )) + .unwrap(), + None + ); + + assert_eq!( + block_on(obf.guarded_write( + (header_addr.clone(), None), + vec![ + (header_addr.clone(), [2]), + (val_addr_1.clone(), [3]), + (val_addr_2.clone(), [3]) + ] + )) + .unwrap(), + Some([2]) + ); + + assert_eq!( + block_on(obf.guarded_write( + (header_addr.clone(), Some([2])), + vec![ + (header_addr.clone(), [4]), + (val_addr_3.clone(), [2]), + (val_addr_4.clone(), [2]) + ] + )) + .unwrap(), + Some([2]) + ); + + assert_eq!( + vec![Some([4]), Some([1]), Some([1]), Some([2]), Some([2])], + block_on(obf.batch_read(vec![ + header_addr, + val_addr_1, + val_addr_2, + val_addr_3, + val_addr_4 + ])) + .unwrap() + ) + } +} diff --git a/src/encoding.rs b/src/encoding.rs index 3beef6a9..8e56e850 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -44,21 +44,39 @@ pub(crate) fn decode>>(_ws: Vec>) -> HashSet>>(op: Op, vs: HashSet) -> Vec> { +pub fn dummy_encode>( + op: Op, + vs: HashSet, +) -> Result, String> { + if (u8::MAX as usize) < WORD_LENGTH { + return Err("WORD_LENGTH too big for this encoding".to_string()); + } + vs.into_iter() - .map(Into::into) - .map(|bytes| { + .map(|v| { + let bytes = v.as_ref(); + if WORD_LENGTH - 2 < bytes.len() { + return Err(format!( + "unsufficient bytes in a word to fit a value of length {}", + bytes.len(), + )); + } + let n = bytes.len() as u8; + let mut res = [0; WORD_LENGTH]; if op == Op::Insert { - [vec![1], bytes].concat() + res[0] = 1; } else { - [vec![0], bytes].concat() + res[0] = 0; } + res[1] = n; + res[2..bytes.len() + 2].copy_from_slice(bytes); + Ok(res) }) .collect() } -pub fn dummy_decode( - ws: Vec>, +pub fn dummy_decode( + ws: Vec<[u8; WORD_LENGTH]>, ) -> Result, TryFromError> where for<'z> Value: Hash + PartialEq + Eq + TryFrom<&'z [u8], Error = TryFromError>, @@ -66,7 +84,8 @@ where let mut res = HashSet::with_capacity(ws.len()); for w in ws { if !w.is_empty() { - let v = Value::try_from(&w[1..])?; + let n = ::from(w[1]); + let v = Value::try_from(&w[2..n + 2])?; if w[0] == 1 { res.insert(v); } else { @@ -77,83 +96,83 @@ where Ok(res) } -#[cfg(test)] -pub mod tests { - use cosmian_crypto_core::{ - bytes_ser_de::to_leb128_len, - reexport::rand_core::{CryptoRngCore, SeedableRng}, - CsRng, - }; - - use super::*; - - pub fn random_uuid(rng: &mut impl CryptoRngCore) -> [u8; 16] { - let mut res = [0; 16]; - rng.fill_bytes(&mut res); - res - } - - fn generate_uuids(n: usize) -> Vec<[u8; 16]> { - let mut rng = CsRng::from_entropy(); - (0..n).map(|_| random_uuid(&mut rng)).collect() - } - - fn random_value(rng: &mut impl CryptoRngCore, max_length: usize) -> Vec { - let length = rng.next_u64() as usize % max_length; - let mut res = vec![0; length]; - rng.fill_bytes(&mut res); - res - } - - fn generate_values(n: usize, max_length: usize) -> Vec> { - let mut rng = CsRng::from_entropy(); - (0..n).map(|_| random_value(&mut rng, max_length)).collect() - } - - fn test_encode_decode_uuids( - encode: fn(Op, HashSet<[u8; 16]>) -> Vec>, - decode: fn(Vec>) -> Result, TryFromError>, - check_len: fn(&HashSet<[u8; 16]>) -> usize, - ) { - for n in 0..100 { - let values = HashSet::from_iter(generate_uuids(n)); - let words = encode(Op::Insert, values.clone()); - assert_eq!(words.len(), check_len(&values)); - let res = decode(words).unwrap(); - assert_eq!(values, res); - } - } - - fn test_encode_decode_variable_length_values( - encode: fn(Op, HashSet>) -> Vec>, - decode: fn(Vec>) -> Result>, TryFromError>, - check_len: fn(&HashSet>) -> usize, - ) { - for max_length in [128, 2048] { - for n in 0..100 { - let values = HashSet::from_iter(generate_values(n, max_length)); - let words = encode(Op::Insert, values.clone()); - assert_eq!(words.len(), check_len(&values)); - let res = decode(words).unwrap(); - assert_eq!(values, res); - } - } - } - - #[test] - fn test_dummy_encoding() { - test_encode_decode_uuids(dummy_encode, dummy_decode, |h| h.len()); - test_encode_decode_variable_length_values(dummy_encode, dummy_decode, |h| h.len()); - } - - fn test_encodings() { - fn compute_expected_length(values: &HashSet>) -> usize { - let total_length = values - .iter() - .map(Vec::len) - .map(|l| to_leb128_len(l) + l) - .sum::(); - (total_length as f64 / (8 * 16) as f64).ceil() as usize - } - } -} +//#[cfg(test)] +//pub mod tests { +//use cosmian_crypto_core::{ +//bytes_ser_de::to_leb128_len, +//reexport::rand_core::{CryptoRngCore, SeedableRng}, +//CsRng, +//}; + +//use super::*; + +//pub fn random_uuid(rng: &mut impl CryptoRngCore) -> [u8; 16] { +//let mut res = [0; 16]; +//rng.fill_bytes(&mut res); +//res +//} + +//fn generate_uuids(n: usize) -> Vec<[u8; 16]> { +//let mut rng = CsRng::from_entropy(); +//(0..n).map(|_| random_uuid(&mut rng)).collect() +//} + +//fn random_value(rng: &mut impl CryptoRngCore, max_length: usize) -> Vec { +//let length = rng.next_u64() as usize % max_length; +//let mut res = vec![0; length]; +//rng.fill_bytes(&mut res); +//res +//} + +//fn generate_values(n: usize, max_length: usize) -> Vec> { +//let mut rng = CsRng::from_entropy(); +//(0..n).map(|_| random_value(&mut rng, max_length)).collect() +//} + +//fn test_encode_decode_uuids( +//encode: fn(Op, HashSet<[u8; 16]>) -> Vec>, +//decode: fn(Vec>) -> Result, TryFromError>, +//check_len: fn(&HashSet<[u8; 16]>) -> usize, +//) { +//for n in 0..100 { +//let values = HashSet::from_iter(generate_uuids(n)); +//let words = encode(Op::Insert, values.clone()); +//assert_eq!(words.len(), check_len(&values)); +//let res = decode(words).unwrap(); +//assert_eq!(values, res); +//} +//} + +//fn test_encode_decode_variable_length_values( +//encode: fn(Op, HashSet>) -> Vec>, +//decode: fn(Vec>) -> Result>, TryFromError>, +//check_len: fn(&HashSet>) -> usize, +//) { +//for max_length in [128, 2048] { +//for n in 0..100 { +//let values = HashSet::from_iter(generate_values(n, max_length)); +//let words = encode(Op::Insert, values.clone()); +//assert_eq!(words.len(), check_len(&values)); +//let res = decode(words).unwrap(); +//assert_eq!(values, res); +//} +//} +//} + +//#[test] +//fn test_dummy_encoding() { +//test_encode_decode_uuids(dummy_encode, dummy_decode, |h| h.len()); +//test_encode_decode_variable_length_values(dummy_encode, dummy_decode, |h| h.len()); +//} + +//fn test_encodings() { +//fn compute_expected_length(values: &HashSet>) -> usize { +//let total_length = values +//.iter() +//.map(Vec::len) +//.map(|l| to_leb128_len(l) + l) +//.sum::(); +//(total_length as f64 / (8 * 16) as f64).ceil() as usize +//} +//} +//} diff --git a/src/error.rs b/src/error.rs index 838cc410..aadce3b6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,11 +5,11 @@ use cosmian_crypto_core::CryptoCoreError; #[derive(Debug)] pub enum Error { Parsing(String), - Encryption(CryptoCoreError), + Crypto(CryptoCoreError), Memory(MemoryError), Conversion(String), - MissingValue(Address), - EncryptionLayer(String), + MissingValue(Address, usize), + CorruptedMemoryCache, } impl Display for Error { diff --git a/src/findex.rs b/src/findex.rs index b9b92bfd..63abc7ca 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -7,50 +7,65 @@ use std::{ use cosmian_crypto_core::{kdf128, CsRng, Secret}; use crate::{ - encoding::Op, error::Error, obf::MemoryEncryptionLayer, ovec::OVec, Address, Index, Stm, - ADDRESS_LENGTH, KEY_LENGTH, + adt::VectorADT, el::MemoryEncryptionLayer, encoding::Op, error::Error, ovec::OVec, Address, + IndexADT, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH, }; -// Lifetime is needed to store a reference of the memory in the vectors. pub struct Findex< - 'a, - Value, + Value: AsRef<[u8]>, + const WORD_LENGTH: usize, TryFromError: std::error::Error, - Memory: 'a + Stm
, Word = Vec>, + Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, > where // values are serializable (but do not depend on `serde`) for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, - Vec: From, + Memory::Error: Send + Sync, { - el: MemoryEncryptionLayer, - vectors: Mutex, OVec<'a, MemoryEncryptionLayer>>>, - encode: Box) -> Vec>>, - decode: Box>) -> Result, TryFromError>>, + el: MemoryEncryptionLayer, + vectors: Mutex< + HashMap< + Address, + OVec>, + >, + >, + encode: Box< + fn( + Op, + HashSet, + ) + -> Result as MemoryADT>::Word>, String>, + >, + decode: Box< + fn( + Vec< as MemoryADT>::Word>, + ) -> Result, TryFromError>, + >, } impl< - 'a, - Value, + Value: Send + Sync + Hash + Eq + AsRef<[u8]>, + const WORD_LENGTH: usize, TryFromError: std::error::Error, - Memory: 'a + Stm
, Word = Vec>, - > Findex<'a, Value, TryFromError, Memory> + Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, + > Findex where for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, Vec: From, + Memory::Error: Send + Sync, { /// Instantiates Findex with the given seed, and memory. pub fn new( seed: Secret, rng: Arc>, - stm: Memory, - encode: fn(Op, HashSet) -> Vec>, - decode: fn(Vec>) -> Result, TryFromError>, + mem: Memory, + encode: fn(Op, HashSet) -> Result, String>, + decode: fn(Vec<[u8; WORD_LENGTH]>) -> Result, TryFromError>, ) -> Self { // TODO: should the RNG be instantiated here? // Creating many instances of Findex would need more work but potentially involve less // waiting for the lock => bench it. Self { - el: MemoryEncryptionLayer::new(seed, rng, stm), + el: MemoryEncryptionLayer::new(seed, rng, mem), vectors: Mutex::new(HashMap::new()), encode: Box::new(encode), decode: Box::new(decode), @@ -59,9 +74,9 @@ where /// Caches this vector for this address. fn bind( - &'a self, + &self, address: Address, - vector: OVec<'a, MemoryEncryptionLayer>, + vector: OVec>, ) { self.vectors .lock() @@ -73,7 +88,7 @@ where fn find( &self, address: &Address, - ) -> Option>> { + ) -> Option>> { self.vectors .lock() .expect("poisoned mutex") @@ -90,13 +105,19 @@ where /// Pushes the given bindings to the vectors associated to the bound keyword. /// /// All vector push operations are performed in parallel (via async calls), not batched. - async fn push>( - &'a self, - bindings: impl Iterator>)>, - ) -> Result<(), Error, as Stm>::Error>> - { + async fn push>( + &self, + op: Op, + bindings: impl Iterator)>, + ) -> Result<(), >::Error> { + let bindings = bindings + .map(|(kw, vals)| (self.encode)(op, vals).map(|words| (kw, words))) + .collect::, String>>() + .map_err(|e| Error::::Conversion(e.to_string()))?; + let futures = bindings - .map(|(kw, vals)| self.vector_push(kw, vals)) + .into_iter() + .map(|(kw, words)| self.vector_push(kw, words)) .collect::>(); // collect for calls do be made for fut in futures { @@ -106,34 +127,30 @@ where Ok(()) } - // TODO: move into push as an async closure when stable. - async fn vector_push>( - &'a self, + // TODO: move this into `push` when async closures are stable. + async fn vector_push>( + &self, kw: Keyword, - values: Vec>, - ) -> Result<(), Error, as Stm>::Error>> - { + values: Vec<[u8; WORD_LENGTH]>, + ) -> Result<(), >::Error> { let a = Self::hash_address(kw.as_ref()); let mut vector = self .find(&a) - .unwrap_or_else(|| OVec::new(a.clone(), &self.el)); + .unwrap_or_else(|| OVec::new(a.clone(), self.el.clone())); vector.push(values).await?; self.bind(a, vector); Ok(()) } - // TODO: move into search as an async closure when stable. - async fn read>( - &'a self, + // TODO: move this into `search` when async closures are stable. + async fn read>( + &self, kw: Keyword, - ) -> Result< - (Keyword, Vec>), - Error, as Stm>::Error>, - > { + ) -> Result<(Keyword, Vec<[u8; WORD_LENGTH]>), >::Error> { let a = Self::hash_address(kw.as_ref()); let vector = self .find(&a) - .unwrap_or_else(|| OVec::new(a.clone(), &self.el)); + .unwrap_or_else(|| OVec::new(a.clone(), self.el.clone())); let words = vector.read().await?; self.bind(a, vector); Ok((kw, words)) @@ -141,20 +158,24 @@ where } impl< - 'a, - Keyword: Hash + PartialEq + Eq + AsRef<[u8]>, - Value: Hash + PartialEq + Eq, + const WORD_LENGTH: usize, + Keyword: Send + Sync + Hash + PartialEq + Eq + AsRef<[u8]>, + Value: Send + Sync + Hash + PartialEq + Eq + AsRef<[u8]>, TryFromError: std::error::Error, - Memory: 'a + Stm
, Word = Vec>, - > Index<'a, Keyword, Value> for Findex<'a, Value, TryFromError, Memory> + Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, + > IndexADT for Findex where for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, Vec: From, + Memory::Error: Send + Sync, { - type Error = Error, as Stm>::Error>; + type Error = Error< + Address, + as MemoryADT>::Error, + >; async fn search( - &'a self, + &self, keywords: impl Iterator, ) -> Result>, Self::Error> { let futures = keywords @@ -174,19 +195,17 @@ where } async fn insert( - &'a self, - bindings: impl Iterator)>, + &self, + bindings: impl Sync + Send + Iterator)>, ) -> Result<(), Self::Error> { - self.push(bindings.map(|(kw, values)| (kw, (self.encode)(Op::Insert, values)))) - .await + self.push(Op::Insert, bindings.into_iter()).await } async fn delete( - &'a self, - bindings: impl Iterator)>, + &self, + bindings: impl Sync + Send + Iterator)>, ) -> Result<(), Self::Error> { - self.push(bindings.map(|(kw, values)| (kw, (self.encode)(Op::Delete, values)))) - .await + self.push(Op::Delete, bindings.into_iter()).await } } @@ -204,9 +223,11 @@ mod tests { address::Address, encoding::{dummy_decode, dummy_encode}, kv::KvStore, - Findex, Index, Value, ADDRESS_LENGTH, + Findex, IndexADT, Value, ADDRESS_LENGTH, }; + const WORD_LENGTH: usize = 16; + #[test] fn test_insert_search_delete_search() { let mut rng = CsRng::from_entropy(); @@ -216,7 +237,7 @@ mod tests { seed, Arc::new(Mutex::new(rng)), kv, - dummy_encode, + dummy_encode::, dummy_decode, ); let bindings = HashMap::<&str, HashSet>::from_iter([ diff --git a/src/kv.rs b/src/kv.rs index ba24abb1..30449fa9 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -5,7 +5,7 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::stm::Stm; +use crate::MemoryADT; #[derive(Debug, Clone, PartialEq, Eq)] pub struct MemoryError; @@ -21,7 +21,16 @@ impl std::error::Error for MemoryError {} #[derive(Clone, Debug, Default)] pub struct KvStore(Arc>>); -impl Stm for KvStore { +impl KvStore { + pub fn clear(&self) { + let store = &mut *self.0.lock().expect("poisoned lock"); + store.clear() + } +} + +impl MemoryADT + for KvStore +{ type Address = Address; type Word = Value; @@ -50,12 +59,25 @@ impl Stm for KvStore IntoIterator + for KvStore +{ + type Item = (Address, Value); + + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.lock().expect("poisoned lock").clone().into_iter() + } +} + #[cfg(test)] mod tests { use futures::executor::block_on; - use crate::Stm; + use crate::MemoryADT; use super::KvStore; diff --git a/src/lib.rs b/src/lib.rs index 60574a9b..80d0de50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,54 +1,23 @@ #![allow(clippy::type_complexity)] mod address; +mod adt; +mod el; mod encoding; mod error; mod findex; mod kv; -mod obf; mod ovec; -mod stm; mod value; -use std::{ - collections::{HashMap, HashSet}, - future::Future, - hash::Hash, -}; - pub use address::Address; +pub use adt::{IndexADT, MemoryADT}; pub use findex::Findex; pub use kv::KvStore; -pub use stm::Stm; pub use value::Value; #[cfg(feature = "bench")] -pub use encoding::{dummy_decode, dummy_encode}; +pub use encoding::{dummy_decode, dummy_encode, Op}; pub const ADDRESS_LENGTH: usize = 16; pub const KEY_LENGTH: usize = 32; - -/// An index stores *bindings*, that associate a keyword with a value. All values bound to the same -/// keyword are said to be *indexed under* this keyword. The number of such values is called the -/// volume of a keyword. -pub trait Index<'a, Keyword: Hash, Value: Hash> { - type Error: std::error::Error; - - /// Search the index for the values bound to the given keywords. - fn search( - &'a self, - keywords: impl Iterator, - ) -> impl Future>, Self::Error>>; - - /// Add the given bindings to the index. - fn insert( - &'a self, - bindings: impl Iterator)>, - ) -> impl Future>; - - /// Remove the given bindings from the index. - fn delete( - &'a self, - bindings: impl Iterator)>, - ) -> impl Future>; -} diff --git a/src/obf.rs b/src/obf.rs deleted file mode 100644 index f9edb16f..00000000 --- a/src/obf.rs +++ /dev/null @@ -1,277 +0,0 @@ -use std::{ - collections::{HashMap, HashSet}, - ops::DerefMut, - sync::{Arc, Mutex, MutexGuard}, -}; - -use crate::{address::Address, error::Error, Stm, ADDRESS_LENGTH, KEY_LENGTH}; -use aes::{ - cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, - Aes256, -}; -use cosmian_crypto_core::{ - Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, Secret, - SymmetricKey, -}; - -#[derive(Debug)] -pub struct MemoryEncryptionLayer, Word = Vec>> { - permutation_key: SymmetricKey, - encryption_key: SymmetricKey<32>, - cache: Arc, HashMap, Vec>>>>, - rng: Arc>, - stm: Memory, -} - -// NOTE: cannot enforce word length at compile time without hard-coding numbers since constant -// generics are not yet allowed in constant operations. -impl, Word = Vec>> MemoryEncryptionLayer { - /// Instantiates a new memory encryption layer. - pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { - let permutation_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); - let encryption_key = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); - Self { - permutation_key, - encryption_key, - cache: Arc::new(Mutex::new(HashMap::new())), - rng, - stm, - } - } - - #[inline(always)] - fn rng(&self) -> MutexGuard { - self.rng.lock().expect("poisoned lock") - } - - /// Retains values cached for the given keys only. - pub fn retain_cached_keys(&self, keys: &HashSet) { - self.cache - .lock() - .expect("poisoned mutex") - .deref_mut() - .retain(|k, _| keys.contains(k)); - } - - /// Decrypts the given value and caches the ciphertext. - fn find_or_encrypt( - &self, - ptx: &[u8], - tok: &Memory::Address, - ) -> Result, Error> { - if let Some(ctx) = self.find(tok, ptx) { - Ok(ctx) - } else { - let ctx = self.encrypt(ptx, tok)?; - self.bind(tok.clone(), ptx.to_vec(), ctx.clone()); - Ok(ctx) - } - } - - /// Decrypts the given value and caches the ciphertext. - fn bind_and_decrypt( - &self, - ctx: Vec, - tok: Memory::Address, - ) -> Result, Error> { - let ptx = self.decrypt(&ctx, &tok)?; - self.bind(tok, ptx.clone(), ctx); - Ok(ptx) - } - - fn encrypt( - &self, - ptx: &[u8], - tok: &Memory::Address, - ) -> Result, Error> { - let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); - let ctx = Aes256Gcm::new(&self.encryption_key) - .encrypt(&nonce, ptx, Some(tok)) - .map_err(Error::Encryption)?; - Ok([nonce.as_bytes(), &ctx].concat()) - } - - #[inline] - fn decrypt( - &self, - ctx: &[u8], - tok: &Memory::Address, - ) -> Result, Error> { - if ctx.len() < Aes256Gcm::NONCE_LENGTH { - return Err(Error::Parsing("ciphertext too small".to_string())); - } - let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) - .map_err(|e| Error::Parsing(e.to_string()))?; - Aes256Gcm::new(&self.encryption_key) - .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(tok)) - .map_err(Error::Encryption) - } - - fn permute(&self, mut a: Memory::Address) -> Memory::Address { - Aes256::new(GenericArray::from_slice(&self.permutation_key)) - .encrypt_block(GenericArray::from_mut_slice(&mut a)); - a - } - - fn bind(&self, tok: Memory::Address, ptx: Vec, ctx: Vec) { - let mut cache = self.cache.lock().expect("poisoned lock"); - if let Some(bindings) = cache.get_mut(&tok) { - bindings.insert(ptx, ctx); - } - } - - fn find(&self, tok: &Address, ref_ptx: &[u8]) -> Option> { - let mut cache = self.cache.lock().expect("poisoned lock"); - if let Some(bindings) = cache.get(tok) { - bindings.get(ref_ptx).cloned() - } else { - cache.insert(tok.clone(), HashMap::new()); - None - } - } -} - -impl, Word = Vec>> Stm - for MemoryEncryptionLayer -{ - type Address = Address; - - type Word = Memory::Word; - - type Error = Error; - - async fn batch_read( - &self, - addresses: Vec, - ) -> Result>, Self::Error> { - let tokens: Vec<_> = addresses.into_iter().map(|a| self.permute(a)).collect(); - let bindings = self.stm.batch_read(tokens.clone()).await?; - bindings - .into_iter() - .zip(tokens) - .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(&ctx, &tok)).transpose()) - .collect() - } - - async fn guarded_write( - &self, - guard: (Self::Address, Option), - bindings: Vec<(Self::Address, Self::Word)>, - ) -> Result, Self::Error> { - let tok = self.permute(guard.0.clone()); - let old = guard.1.and_then(|ptx| self.find(&tok, &ptx)); - let bindings = bindings - .into_iter() - .map(|(a, v)| { - let tok = self.permute(a); - self.find_or_encrypt(&v, &tok).map(|ctx| (tok, ctx)) - }) - .collect::, _>>()?; - let cur = self.stm.guarded_write((tok.clone(), old), bindings).await?; - cur.map(|ctx| self.bind_and_decrypt(ctx, tok)).transpose() - } -} - -#[cfg(test)] -mod tests { - use std::sync::{Arc, Mutex}; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; - use futures::executor::block_on; - - use crate::{ - address::Address, - kv::KvStore, - obf::{MemoryEncryptionLayer, ADDRESS_LENGTH}, - stm::Stm, - }; - - #[test] - fn test_encrypt_decrypt() { - let mut rng = CsRng::from_entropy(); - let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); - let tok = Address::::random(&mut rng); - let ptx = vec![1]; - let ctx = obf.encrypt(&ptx, &tok).unwrap(); - let res = obf.decrypt(&ctx, &tok).unwrap(); - assert_eq!(ptx.len(), res.len()); - assert_eq!(ptx, res); - } - - /// Ensures a transaction can express an vector push operation: - /// - the counter is correctly incremented and all values are written; - /// - using the wrong value in the guard fails the operation and returns the current value. - #[test] - fn test_vector_push() { - let mut rng = CsRng::from_entropy(); - let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); - - let header_addr = Address::::random(&mut rng); - - let val_addr_1 = Address::::random(&mut rng); - let val_addr_2 = Address::::random(&mut rng); - let val_addr_3 = Address::::random(&mut rng); - let val_addr_4 = Address::::random(&mut rng); - - assert_eq!( - block_on(obf.guarded_write( - (header_addr.clone(), None), - vec![ - (header_addr.clone(), vec![2]), - (val_addr_1.clone(), vec![1]), - (val_addr_2.clone(), vec![1]) - ] - )) - .unwrap(), - None - ); - - assert_eq!( - block_on(obf.guarded_write( - (header_addr.clone(), None), - vec![ - (header_addr.clone(), vec![2]), - (val_addr_1.clone(), vec![3]), - (val_addr_2.clone(), vec![3]) - ] - )) - .unwrap(), - Some(vec![2]) - ); - - assert_eq!( - block_on(obf.guarded_write( - (header_addr.clone(), Some(vec![2])), - vec![ - (header_addr.clone(), vec![4]), - (val_addr_3.clone(), vec![2]), - (val_addr_4.clone(), vec![2]) - ] - )) - .unwrap(), - Some(vec![2]) - ); - - assert_eq!( - vec![ - Some(vec![4]), - Some(vec![1]), - Some(vec![1]), - Some(vec![2]), - Some(vec![2]) - ], - block_on(obf.batch_read(vec![ - header_addr, - val_addr_1, - val_addr_2, - val_addr_3, - val_addr_4 - ])) - .unwrap() - ) - } -} diff --git a/src/ovec.rs b/src/ovec.rs index f9956e36..500199e7 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -1,24 +1,31 @@ use std::{fmt::Debug, hash::Hash, ops::Add}; -use crate::{error::Error, Stm}; +use crate::{adt::VectorADT, error::Error, MemoryADT}; +/// Headers contain a counter of the number of values stored in the vector. #[derive(Debug, Clone, Default, PartialEq, Eq)] struct Header { - pub(crate) start: u64, - pub(crate) stop: u64, + pub(crate) cnt: u64, } -impl From<&Header> for Vec { - fn from(header: &Header) -> Self { - let stop = header.stop.to_be_bytes(); - let start = header.start.to_be_bytes(); - [start, stop].concat() +impl TryFrom<&Header> for [u8; WORD_LENGTH] { + type Error = String; + + fn try_from(header: &Header) -> Result { + if WORD_LENGTH < 8 { + return Err("insufficient word length: should be at least 16 bytes".to_string()); + } + let mut res = [0; WORD_LENGTH]; + res[..8].copy_from_slice(&header.cnt.to_be_bytes()); + Ok(res) } } -impl From
for Vec { - fn from(value: Header) -> Self { - Self::from(&value) +impl TryFrom
for [u8; WORD_LENGTH] { + type Error = String; + + fn try_from(value: Header) -> Result { + Self::try_from(&value) } } @@ -26,85 +33,113 @@ impl TryFrom<&[u8]> for Header { type Error = String; fn try_from(value: &[u8]) -> Result { - if value.len() < 16 { + if value.len() < 8 { return Err(format!( "value to short to be converted to header: need {}, got {}", 16, value.len() )); } - let start = <[u8; 8]>::try_from(&value[..8]).expect("length is correct"); - let stop = <[u8; 8]>::try_from(&value[8..]).expect("length is correct"); + let cnt = <[u8; 8]>::try_from(&value[..8]).expect("length is correct"); Ok(Self { - start: u64::from_be_bytes(start), - stop: u64::from_be_bytes(stop), + cnt: u64::from_be_bytes(cnt), }) } } -/// A client-side implementation of a vector. +/// Implementation of a vector using an infinite array (we consider all addresses from a to a + ∞ +/// are allocated to the vector. /// /// ```txt -/// +------------distant-memory-----------+ -/// | | -/// a --|--> | header (h) | v_1 | ... | v_n | | -/// | | -/// +------------distant-memory-----------+ +/// +------------+-----+-----+-----+ +/// | header (h) | v_0 | ... | v_n | +/// +------------+-----+-----+-----+ +/// a a+1 ... a+n+1 /// ``` #[derive(Debug)] -pub struct OVec<'a, Memory: Stm>> { +pub struct OVec> { a: Memory::Address, h: Option
, - m: &'a Memory, + m: Memory, } -impl<'a, Address: Clone, Memory: Stm
>> Clone - for OVec<'a, Memory> +impl< + const WORD_LENGTH: usize, + Address: Clone, + Memory: Clone + MemoryADT
, + > Clone for OVec { fn clone(&self) -> Self { Self { a: self.a.clone(), h: self.h.clone(), - m: self.m, + m: self.m.clone(), } } } impl< - 'a, + const WORD_LENGTH: usize, Address: Hash + Eq + Debug + Clone + Add, - Memory: Stm
>, - > OVec<'a, Memory> + Memory: Clone + MemoryADT
, + > OVec { /// (Lazily) instantiates a new vector at this address in this memory: no value is written /// before the first push. - pub fn new(a: Address, m: &'a Memory) -> Self { + pub fn new(a: Address, m: Memory) -> Self { Self { a, h: None, m } } +} - /// Atomically pushes the given values at the end of this vector. Retries upon conflict. - pub async fn push( - &mut self, - values: Vec>, - ) -> Result<(), Error> { +impl< + const WORD_LENGTH: usize, + Address: Send + Sync + Hash + Eq + Debug + Clone + Add, + Memory: Send + Sync + Clone + MemoryADT
, + > VectorADT for OVec +where + Memory::Error: Send + Sync, +{ + type Value = Memory::Word; + + type Error = Error; + + async fn push(&mut self, vs: Vec) -> Result<(), Self::Error> { + // Findex modifications are only lock-free, hence it does not guarantee a given client will + // ever terminate. + // + // TODO: this loop will arguably terminate if the index is not highly contended, but we + // need a stronger guarantee. Maybe a return with an error after a reaching a certain + // number of retries. + let mut old = self.h.clone(); loop { - let old = self.h.as_ref(); let (cur, new) = { // Generates a new header which counter is incremented. - let mut new = old.cloned().unwrap_or_default(); - new.stop += values.len() as u64; + let mut new = old.clone().unwrap_or_default(); + new.cnt += vs.len() as u64; // Binds the correct addresses to the values. - let mut bindings = (new.stop - values.len() as u64..new.stop) - .zip(values.clone()) - .map(|(i, v)| (self.a.clone() + 1 + i, v)) + let mut bindings = (new.cnt - vs.len() as u64..new.cnt) + .zip(vs.clone()) + .map(|(i, v)| (self.a.clone() + 1 + i, v)) // a is the header address .collect::>(); - bindings.push((self.a.clone(), (&new).into())); + bindings.push(( + self.a.clone(), + (&new).try_into().map_err(|e| Error::Conversion(e))?, + )); // Attempts committing the new bindings using the old header as guard. let cur = self .m - .guarded_write((self.a.clone(), old.map(>::from)), bindings) + .guarded_write( + ( + self.a.clone(), + old.clone() + .map(<[u8; WORD_LENGTH]>::try_from) + .transpose() + .map_err(|e| Error::Conversion(e))?, + ), + bindings, + ) .await? .map(|v| Header::try_from(v.as_slice())) .transpose() @@ -112,115 +147,80 @@ impl< (cur, new) }; - if cur.as_ref() == old { + if cur.as_ref() == old.as_ref() { self.h = Some(new); return Ok(()); } else { - self.h = cur; - // Findex modifications are only lock-free, hence it does not guarantee a given - // client will ever terminate. - // - // TODO: this loop will arguably terminate if the index is not highly contended, - // but we need a stronger guarantee. Maybe a return with an error after a reaching - // a certain number of retries. + old = cur; } } } - /// Atomically reads the values stored in this vector. - pub async fn read(&self) -> Result>, Error> { + async fn read(&self) -> Result, Self::Error> { // Read from a first batch of addresses: // - the header address; // - the value addresses derived from the known header. let old_header = self.h.clone().unwrap_or_default(); let addresses = [self.a.clone()] .into_iter() - .chain((old_header.start..old_header.stop).map(|i| self.a.clone() + i + 1)) + .chain((0..old_header.cnt).map(|i| self.a.clone() + i + 1)) .collect(); - let res = self.m.batch_read(addresses).await?; - - let cur_header = res[0] - .clone() - .map(|v| Header::try_from(v.as_slice())) - .transpose() - .map_err(Error::Conversion)? - .unwrap_or_default(); + let first_batch = self.m.batch_read(addresses).await?; - // Get all missing values, if any. - let missing_addresses = (cur_header.start.max(old_header.stop)..cur_header.stop) - .map(|i| self.a.clone() + i + 1) - .collect::>(); + let second_batch = { + let cur_header = first_batch[0] + .map(|v| Header::try_from(v.as_slice())) + .transpose() + .map_err(Error::Conversion)? + .unwrap_or_default(); - let missing_values = if missing_addresses.is_empty() { - vec![] // only call the memory a second time if needed - } else { - self.m.batch_read(missing_addresses).await? + if old_header.cnt < cur_header.cnt { + // Get all missing values, if any. + let missing_addresses = (old_header.cnt..cur_header.cnt) + .map(|i| self.a.clone() + i + 1) + .collect::>(); + self.m.batch_read(missing_addresses).await? + } else { + vec![] // only call the memory a second time if needed + } }; - res.into_iter() + first_batch + .into_iter() .skip(1) - .chain(missing_values) + .chain(second_batch) .enumerate() - .map(|(i, v)| v.ok_or_else(|| Error::MissingValue(self.a.clone() + i as u64))) + .map(|(i, v)| v.ok_or_else(|| Error::MissingValue(self.a.clone(), i))) .collect() } } #[cfg(test)] mod tests { - use futures::executor::block_on; - use std::sync::{Arc, Mutex}; - - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; - use crate::{ - address::Address, kv::KvStore, obf::MemoryEncryptionLayer, ovec::OVec, ADDRESS_LENGTH, + address::Address, + adt::tests::{test_vector_concurrent, test_vector_sequential}, + el::MemoryEncryptionLayer, + kv::KvStore, + ovec::OVec, + ADDRESS_LENGTH, }; + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + use std::sync::{Arc, Mutex}; - #[test] - fn test_vector_push_with_shared_cache() { - let mut rng = CsRng::from_entropy(); - let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); - let address = Address::random(&mut rng); - let mut vector1 = OVec::new(address.clone(), &obf); - let mut vector2 = OVec::new(address.clone(), &obf); - - let values = (0..10).map(|n| vec![n]).collect::>(); - block_on(vector1.push(values[..5].to_vec())).unwrap(); - block_on(vector2.push(values[..5].to_vec())).unwrap(); - block_on(vector1.push(values[5..].to_vec())).unwrap(); - block_on(vector2.push(values[5..].to_vec())).unwrap(); - assert_eq!( - [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), - block_on(vector1.read()).unwrap() - ); - } + const WORD_LENGTH: usize = 16; - #[test] - fn test_vector_push_without_shared_cache() { + #[tokio::test(flavor = "multi_thread", worker_threads = 8)] + async fn test_ovec() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf1 = - MemoryEncryptionLayer::new(seed.clone(), Arc::new(Mutex::new(rng.clone())), kv.clone()); - let obf2 = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv.clone()); let address = Address::random(&mut rng); - let mut vector1 = OVec::new(address.clone(), &obf1); - let mut vector2 = OVec::new(address.clone(), &obf2); - - let values = (0..10).map(|n| vec![n]).collect::>(); - block_on(vector1.push(values[..5].to_vec())).unwrap(); - // vector2 should fail its first attempt. - block_on(vector2.push(values[..5].to_vec())).unwrap(); - block_on(vector1.push(values[5..].to_vec())).unwrap(); - // vector2 should fail its first attempt. - block_on(vector2.push(values[5..].to_vec())).unwrap(); - assert_eq!( - [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), - block_on(vector1.read()).unwrap() - ); + let v = OVec::::new(address.clone(), obf); + test_vector_sequential(&v).await; + kv.clear(); + test_vector_concurrent(&v).await; } } diff --git a/src/stm.rs b/src/stm.rs deleted file mode 100644 index 11b90612..00000000 --- a/src/stm.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::future::Future; - -/// A Software Transactional Memory: all operations exposed are atomic. -pub trait Stm { - /// Address space. - type Address; - - /// Word space. - type Word; - - /// Memory error. - type Error: std::error::Error; - - /// Reads the words from the given addresses. - fn batch_read( - &self, - a: Vec, - ) -> impl Future>, Self::Error>>; - - /// Write the given words at the given addresses if the word currently stored at the guard - /// address is the one given, and returns this guard word. - fn guarded_write( - &self, - guard: (Self::Address, Option), - tasks: Vec<(Self::Address, Self::Word)>, - ) -> impl Future, Self::Error>>; -} diff --git a/src/value.rs b/src/value.rs index c35c063e..d6f8572b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,6 +8,12 @@ use std::string::FromUtf8Error; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Value(Vec); +impl AsRef<[u8]> for Value { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + impl From<&[u8]> for Value { fn from(value: &[u8]) -> Self { Self(value.to_vec()) From 0d986a98822fef0b88e7818c916570bb942e47e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Fri, 5 Jul 2024 12:30:18 +0200 Subject: [PATCH 13/42] refine benches --- benches/benches.rs | 53 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 4511a847..60ac4bfb 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -6,7 +6,7 @@ use std::{ }; use cosmian_crypto_core::{ - reexport::rand_core::{CryptoRngCore, SeedableRng}, + reexport::rand_core::{CryptoRngCore, RngCore, SeedableRng}, CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; @@ -122,11 +122,11 @@ fn bench_search(c: &mut Criterion) { fn bench_insert(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); - let index = build_benchmarking_bindings_index(&mut rng); let seed = Secret::random(&mut rng); // Bench the impact of the binding multiplicity. { + let index = build_benchmarking_bindings_index(&mut rng); let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); for (i, (kw, vals)) in index.clone().into_iter().enumerate() { let n = 10i32.pow(i as u32) as usize; @@ -182,25 +182,54 @@ fn bench_insert(c: &mut Criterion) { // Bench the impact of the keyword multiplicity. { - let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); - for (i, (kw, vals)) in index.clone().into_iter().enumerate() { - let n = 10i32.pow(i as u32) as usize; + let mut group = c.benchmark_group("Multiple keywords insert (one binding each)"); + for i in 0..4 { + let n = 10usize.pow(i); group - .bench_function(BenchmarkId::from_parameter(n), |b| { + .bench_function(format!("inserting {n} words to memory"), |b| { b.iter_batched( || { - let rng = CsRng::from_entropy(); let seed = seed.clone(); - let vals = vals.clone(); + let stm = KvStore::default(); + let bindings = (0..2 * n) + .map(|_| { + let mut a = [0; 16]; + let mut w = [0; 16]; + rng.fill_bytes(&mut a); + rng.fill_bytes(&mut w); + (a, w) + }) + .collect::>(); + (stm, bindings) + }, + |(stm, bindings)| { + block_on(stm.guarded_write(([0; 16], None), bindings)) + .expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + group + .bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || { let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng)), + seed.clone(), + Arc::new(Mutex::new(rng.clone())), KvStore::default(), dummy_encode::<16, _>, dummy_decode, ); - let bindings = [(kw, vals)].into_iter(); - (findex, bindings) + let bindings = (0..n) + .map(|_| { + ( + rng.next_u64().to_be_bytes(), + HashSet::from_iter([rng.next_u64().to_be_bytes()]), + ) + }) + .collect::>(); + (findex, bindings.into_iter()) }, |(findex, bindings)| { block_on(findex.insert(bindings)).expect("search failed"); From acce3c3e6299e2c3730ce9decdc063bde3ba0dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 8 Jul 2024 11:11:55 +0200 Subject: [PATCH 14/42] add doc and rename OVec to IVec --- src/address.rs | 3 +- src/adt.rs | 111 ++++++++++++++++------------- src/encoding.rs | 92 +++++++++++++++++------- src/{el.rs => encryption_layer.rs} | 2 +- src/error.rs | 5 +- src/findex.rs | 30 ++++---- src/lib.rs | 20 +++++- src/ovec.rs | 44 +++++++----- 8 files changed, 191 insertions(+), 116 deletions(-) rename src/{el.rs => encryption_layer.rs} (99%) diff --git a/src/address.rs b/src/address.rs index 8385ec30..51e39e2c 100644 --- a/src/address.rs +++ b/src/address.rs @@ -2,6 +2,7 @@ use std::ops::{Add, Deref, DerefMut}; use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; +// NOTE: a more efficient implementation of the address could be a big-int. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Address([u8; LENGTH]); @@ -36,7 +37,7 @@ impl Address { impl Add for Address { type Output = Address; - /// Highly inefficient implementation of a add modulo 2^8^LENGTH in little endian. + /// Highly inefficient implementation of an add modulo 2^8^LENGTH in little endian. fn add(mut self, mut adder: u64) -> Self::Output { let mut carry = 0; let mut pos = 0; diff --git a/src/adt.rs b/src/adt.rs index d808f7b8..bef20a25 100644 --- a/src/adt.rs +++ b/src/adt.rs @@ -1,3 +1,10 @@ +//! We define here the main abstractions used in this crate, namely: +//! - the index ADT; +//! - the vector ADT; +//! - the memory ADT. +//! +//! Each of them strive for simplicity and consistency with the classical CS notions. + use std::{ collections::{HashMap, HashSet}, future::Future, @@ -75,57 +82,63 @@ pub trait MemoryADT { #[cfg(test)] pub(crate) mod tests { - use futures::{executor::block_on, future::join_all}; - - use super::*; - - /// Adding information from different copies of the same vector should be visible by all - /// copies. - pub async fn test_vector_sequential( - v: &(impl Clone + VectorADT), - ) { - let mut v1 = v.clone(); - let mut v2 = v.clone(); - let values = (0..10).map(|n| [n; LENGTH]).collect::>(); - v1.push(values[..5].to_vec()).await.unwrap(); - v2.push(values[..5].to_vec()).await.unwrap(); - v1.push(values[5..].to_vec()).await.unwrap(); - v2.push(values[5..].to_vec()).await.unwrap(); - assert_eq!( - [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), - v.read().await.unwrap() - ); - } + pub use vector::*; + + mod vector { + //! This module defines tests any implementation of the VectorADT interface must pass. + + use crate::adt::VectorADT; + use futures::{executor::block_on, future::join_all}; + + /// Adding information from different copies of the same vector should be visible by all + /// copies. + pub async fn test_vector_sequential( + v: &(impl Clone + VectorADT), + ) { + let mut v1 = v.clone(); + let mut v2 = v.clone(); + let values = (0..10).map(|n| [n; LENGTH]).collect::>(); + v1.push(values[..5].to_vec()).await.unwrap(); + v2.push(values[..5].to_vec()).await.unwrap(); + v1.push(values[5..].to_vec()).await.unwrap(); + v2.push(values[5..].to_vec()).await.unwrap(); + assert_eq!( + [&values[..5], &values[..5], &values[5..], &values[5..]].concat(), + v.read().await.unwrap() + ); + } - pub async fn test_vector_concurrent< - const LENGTH: usize, - V: 'static + Clone + VectorADT, - >( - v: &V, - ) { - let n = 100; - let m = 2; - let values = (0..n * m).map(|i| [i as u8; LENGTH]).collect::>(); - let handles = values - .chunks_exact(m) - .map(|vals| { - let vals = vals.to_vec(); - let mut vec = v.clone(); - tokio::spawn(async move { - for val in vals { - vec.push(vec![val]).await.unwrap(); - } + /// Concurrently adding data to instances of the same vector should not introduce data loss. + pub async fn test_vector_concurrent< + const LENGTH: usize, + V: 'static + Clone + VectorADT, + >( + v: &V, + ) { + let n = 100; + let m = 2; + let values = (0..n * m).map(|i| [i as u8; LENGTH]).collect::>(); + let handles = values + .chunks_exact(m) + .map(|vals| { + let vals = vals.to_vec(); + let mut vec = v.clone(); + tokio::spawn(async move { + for val in vals { + vec.push(vec![val]).await.unwrap(); + } + }) }) - }) - .collect::>(); - for h in join_all(handles).await { - h.unwrap(); + .collect::>(); + for h in join_all(handles).await { + h.unwrap(); + } + let mut res = block_on(v.read()).unwrap(); + let old = res.clone(); + res.sort(); + assert_ne!(old, res); + assert_eq!(res.len(), n * m); + assert_eq!(res, values); } - let mut res = block_on(v.read()).unwrap(); - let old = res.clone(); - res.sort(); - assert_ne!(old, res); - assert_eq!(res.len(), n * m); - assert_eq!(res, values); } } diff --git a/src/encoding.rs b/src/encoding.rs index 8e56e850..6462badd 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,3 +1,7 @@ +//! This module defines encoding operations that are used to serialize an operation. +//! Currently, the only supported operations are the insertion and deletion, but there is no +//! theorical restriction on the kind of operation that can be used. + #![allow(dead_code)] use std::{cmp::Ordering, collections::HashSet, hash::Hash}; @@ -13,35 +17,69 @@ pub enum Mode { Offset(usize), } -const BLOCK_LENGTH: usize = 16; -const CHUNK_LENGTH: usize = 8 * BLOCK_LENGTH; - -fn is_admissible_eq_block_mode_value(v: &[u8]) -> bool { - (v.len() % BLOCK_LENGTH == 0) && (v.len() < CHUNK_LENGTH) -} - -fn is_eq_block_mode(_vs: &[Vec]) -> Mode { - todo!() -} - -pub(crate) fn encode>>(_op: Op, vs: HashSet) -> Vec> { - let mut serialized_values = vs.into_iter().map(Into::into).collect::>(); - // We sort the values in order to maximize the number of block mode chunks. - // This is an educated guess, maybe work on a quantification. - serialized_values.sort_by(|lhs, rhs| { - if lhs.len() < rhs.len() { - Ordering::Less - } else { - Ordering::Greater - } - }); +mod one_byte_metadata_uid_optimized { + //! This module defines a compact (only one systematic metadata byte) encoding that can be used + //! to serialize up to two different operations. It defines two *modes*: + //! - the *block mode*, in which words are composed of multiple values encoded in an equal + //! number of blocks. The size of these blocks (16 bytes) has been chosen so that UIDs can + //! fit in a single block, thus introducing an only byte of overhead on words storing + //! operations on UIDs only. The size of a word has been chosen to maximize the use of this + //! metadata byte. Reducing the number of blocks per word would decrease the impact of + //! padding on partially filled words, while increasing the impact on this metadata byte on + //! the overall storage. + //! - the *default mode*, in which each value written is prepended with its length, encoded + //! using the LEB128 format. Values may be written across words to avoid unnecessary padding. + //! + //! + //! Using a single byte of metadata per word means that the overhead in block-mode is only + //! 0.78% (without counting eventual paddings). + //! + //! + //! In order to minimize the overhead, values may be sorted before being encoded in the attempt + //! to maximize the use of the block mode (for example sorting all 16-byte values next to each + //! other, all 32-byte ones too etc). + //! + //! The structure of the metadata byte is as follows: + //! - the operation bit: designates which operation is being performed on the encoded values; + //! - the mode bit: designates the mode in use; + //! + //! The meaning of the following 6 bits is mode-dependant: + //! - in the default mode, they designate the number of values encoded in this word. Up to 64 + //! values can be encoded in a single word, which is the limit given the available size per + //! word (128 bytes), and that prepending values by their LEB128 length adds a least on byte + //! of overhead, making the cost of storing one-byte values 2 bytes. + //! - in the block mode, they designate the block-length (2 bits) of the values stored in this + //! word, and the number of such values stored in this word (3 bits). + + use super::*; + + /// Blocks are the smallest unit size in block mode, 16 bytes is optimized to store UUIDs. + const BLOCK_LENGTH: usize = 16; + + /// The chunk length is the size of the available space in a word. + const CHUNK_LENGTH: usize = 8 * BLOCK_LENGTH; + + const WORD_LENGTH: usize = 1 + CHUNK_LENGTH; + + pub(crate) fn encode>>(_op: Op, vs: HashSet) -> Vec> { + let mut serialized_values = vs.into_iter().map(Into::into).collect::>(); + // We sort the values in order to maximize the number of block mode chunks. + // This is an educated guess, maybe work on a quantification. + serialized_values.sort_by(|lhs, rhs| { + if lhs.len() < rhs.len() { + Ordering::Less + } else { + Ordering::Greater + } + }); - // Use a greedy algorithm to generate chunks. - todo!() -} + // Use a greedy algorithm to generate chunks. + todo!() + } -pub(crate) fn decode>>(_ws: Vec>) -> HashSet { - todo!() + pub(crate) fn decode>>(_ws: Vec>) -> HashSet { + todo!() + } } pub fn dummy_encode>( diff --git a/src/el.rs b/src/encryption_layer.rs similarity index 99% rename from src/el.rs rename to src/encryption_layer.rs index 90b61faa..9e8ca225 100644 --- a/src/el.rs +++ b/src/encryption_layer.rs @@ -234,7 +234,7 @@ mod tests { use crate::{ address::Address, - el::{MemoryEncryptionLayer, ADDRESS_LENGTH}, + encryption_layer::{MemoryEncryptionLayer, ADDRESS_LENGTH}, kv::KvStore, MemoryADT, }; diff --git a/src/error.rs b/src/error.rs index aadce3b6..ba3d5985 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,10 +14,7 @@ pub enum Error { impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Parsing(e) => write!(f, "{}", e), - _ => write!(f, "Error"), - } + write!(f, "{:?}", self) } } diff --git a/src/findex.rs b/src/findex.rs index 63abc7ca..a4402a7a 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -7,25 +7,25 @@ use std::{ use cosmian_crypto_core::{kdf128, CsRng, Secret}; use crate::{ - adt::VectorADT, el::MemoryEncryptionLayer, encoding::Op, error::Error, ovec::OVec, Address, - IndexADT, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH, + adt::VectorADT, encoding::Op, encryption_layer::MemoryEncryptionLayer, error::Error, + ovec::IVec, Address, IndexADT, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH, }; pub struct Findex< - Value: AsRef<[u8]>, const WORD_LENGTH: usize, + Value, TryFromError: std::error::Error, Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, > where // values are serializable (but do not depend on `serde`) - for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, Memory::Error: Send + Sync, { el: MemoryEncryptionLayer, vectors: Mutex< HashMap< Address, - OVec>, + IVec>, >, >, encode: Box< @@ -43,13 +43,13 @@ pub struct Findex< } impl< - Value: Send + Sync + Hash + Eq + AsRef<[u8]>, const WORD_LENGTH: usize, + Value: Send + Sync + Hash + Eq, TryFromError: std::error::Error, Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, - > Findex + > Findex where - for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, Vec: From, Memory::Error: Send + Sync, { @@ -76,7 +76,7 @@ where fn bind( &self, address: Address, - vector: OVec>, + vector: IVec>, ) { self.vectors .lock() @@ -88,7 +88,7 @@ where fn find( &self, address: &Address, - ) -> Option>> { + ) -> Option>> { self.vectors .lock() .expect("poisoned mutex") @@ -136,7 +136,7 @@ where let a = Self::hash_address(kw.as_ref()); let mut vector = self .find(&a) - .unwrap_or_else(|| OVec::new(a.clone(), self.el.clone())); + .unwrap_or_else(|| IVec::new(a.clone(), self.el.clone())); vector.push(values).await?; self.bind(a, vector); Ok(()) @@ -150,7 +150,7 @@ where let a = Self::hash_address(kw.as_ref()); let vector = self .find(&a) - .unwrap_or_else(|| OVec::new(a.clone(), self.el.clone())); + .unwrap_or_else(|| IVec::new(a.clone(), self.el.clone())); let words = vector.read().await?; self.bind(a, vector); Ok((kw, words)) @@ -160,12 +160,12 @@ where impl< const WORD_LENGTH: usize, Keyword: Send + Sync + Hash + PartialEq + Eq + AsRef<[u8]>, - Value: Send + Sync + Hash + PartialEq + Eq + AsRef<[u8]>, + Value: Send + Sync + Hash + PartialEq + Eq, TryFromError: std::error::Error, Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, - > IndexADT for Findex + > IndexADT for Findex where - for<'z> Value: TryFrom<&'z [u8], Error = TryFromError>, + for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, Vec: From, Memory::Error: Send + Sync, { diff --git a/src/lib.rs b/src/lib.rs index 80d0de50..58679497 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,22 +2,36 @@ mod address; mod adt; -mod el; mod encoding; +mod encryption_layer; mod error; mod findex; -mod kv; mod ovec; mod value; pub use address::Address; pub use adt::{IndexADT, MemoryADT}; pub use findex::Findex; -pub use kv::KvStore; pub use value::Value; +#[cfg(any(test, feature = "bench"))] +mod kv; #[cfg(feature = "bench")] pub use encoding::{dummy_decode, dummy_encode, Op}; +#[cfg(feature = "bench")] +pub use kv::KvStore; +/// 16-byte addresses ensure a high collision resistance that poses virtually no limitation on the +/// index. +/// +/// 8-byte addresses can also be used for smaller indexes if storage is the limiting factor, in +/// which case the number of addresses in used at which collisions are to be expected is +/// approximately 2^32 (see the birthday paradox for more details). Keyword collision can be +/// mitigated by marking n bit of the addresses, allowing to statistically store up to 2^((64-n)/2) +/// keywords, and reducing the number of words that can be used to store associated values to +/// sqrt(2^64 - 2^n). pub const ADDRESS_LENGTH: usize = 16; + +/// Using 32-byte cryptographic keys allow achieving post-quantum resistance if the adequate +/// primitives are used (e.g. AES). pub const KEY_LENGTH: usize = 32; diff --git a/src/ovec.rs b/src/ovec.rs index 500199e7..38e12097 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -1,8 +1,26 @@ +//! This module implements a simple vector, defined as a data-structure that preserves the +//! following invariant: +//! +//! > I_v: the value of the counter stored at the vector address is equal to the number of values +//! stored in this vector; these values are of homogeneous type and stored in contiguous +//! memory words. +//! +//! This implementation is based on the assumption that an infinite array starting at the vector's +//! address has been allocated, and thus stores values after the header: +//! +//! ```txt +//! +------------+-----+-----+-----+ +//! | header (h) | v_0 | ... | v_n | +//! +------------+-----+-----+-----+ +//! a a+1 ... a+n+1 +//! ``` + use std::{fmt::Debug, hash::Hash, ops::Add}; use crate::{adt::VectorADT, error::Error, MemoryADT}; /// Headers contain a counter of the number of values stored in the vector. +// TODO: header could store metadata (e.g. sparsity budget) #[derive(Debug, Clone, Default, PartialEq, Eq)] struct Header { pub(crate) cnt: u64, @@ -47,18 +65,12 @@ impl TryFrom<&[u8]> for Header { } } -/// Implementation of a vector using an infinite array (we consider all addresses from a to a + ∞ -/// are allocated to the vector. -/// -/// ```txt -/// +------------+-----+-----+-----+ -/// | header (h) | v_0 | ... | v_n | -/// +------------+-----+-----+-----+ -/// a a+1 ... a+n+1 -/// ``` +/// Implementation of a vector in an infinite array. #[derive(Debug)] -pub struct OVec> { +pub struct IVec> { + // backing array address a: Memory::Address, + // cached header value h: Option
, m: Memory, } @@ -67,7 +79,7 @@ impl< const WORD_LENGTH: usize, Address: Clone, Memory: Clone + MemoryADT
, - > Clone for OVec + > Clone for IVec { fn clone(&self) -> Self { Self { @@ -82,7 +94,7 @@ impl< const WORD_LENGTH: usize, Address: Hash + Eq + Debug + Clone + Add, Memory: Clone + MemoryADT
, - > OVec + > IVec { /// (Lazily) instantiates a new vector at this address in this memory: no value is written /// before the first push. @@ -95,7 +107,7 @@ impl< const WORD_LENGTH: usize, Address: Send + Sync + Hash + Eq + Debug + Clone + Add, Memory: Send + Sync + Clone + MemoryADT
, - > VectorADT for OVec + > VectorADT for IVec where Memory::Error: Send + Sync, { @@ -201,9 +213,9 @@ mod tests { use crate::{ address::Address, adt::tests::{test_vector_concurrent, test_vector_sequential}, - el::MemoryEncryptionLayer, + encryption_layer::MemoryEncryptionLayer, kv::KvStore, - ovec::OVec, + ovec::IVec, ADDRESS_LENGTH, }; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; @@ -218,7 +230,7 @@ mod tests { let kv = KvStore::, Vec>::default(); let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv.clone()); let address = Address::random(&mut rng); - let v = OVec::::new(address.clone(), obf); + let v = IVec::::new(address.clone(), obf); test_vector_sequential(&v).await; kv.clear(); test_vector_concurrent(&v).await; From 301c1b282b1227827806a3c602a0aa6a1f7dacdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 8 Jul 2024 16:09:12 +0200 Subject: [PATCH 15/42] Correct word size in benches, and precompute AE(S) schemes --- benches/benches.rs | 14 ++++++++------ src/encryption_layer.rs | 23 +++++++++++++---------- src/kv.rs | 8 +++++++- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 60ac4bfb..c037be80 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -13,6 +13,8 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; use futures::executor::block_on; +const WORD_LENGTH: usize = 1 + 8 * 16; + /// Builds an index that associates each `kw_i` to `10^i` values, both random 64-bit values. fn build_benchmarking_bindings_index( rng: &mut impl CryptoRngCore, @@ -52,7 +54,7 @@ fn bench_search(c: &mut Criterion) { seed.clone(), Arc::new(Mutex::new(rng.clone())), stm, - dummy_encode::<16, _>, + dummy_encode::, dummy_decode, ); block_on(findex.insert(index.clone().into_iter())).unwrap(); @@ -80,7 +82,7 @@ fn bench_search(c: &mut Criterion) { seed, Arc::new(Mutex::new(rng)), stm.clone(), - dummy_encode::<16, _>, + dummy_encode::, dummy_decode, ); block_on(findex.insert(index.clone().into_iter())).unwrap(); @@ -138,7 +140,7 @@ fn bench_insert(c: &mut Criterion) { let seed = seed.clone(); let vals = vals.clone(); let stm = KvStore::default(); - let words = dummy_encode::<16, _>(Op::Insert, vals).unwrap(); + let words = dummy_encode::(Op::Insert, vals).unwrap(); let bindings = words .into_iter() .enumerate() @@ -164,7 +166,7 @@ fn bench_insert(c: &mut Criterion) { seed, Arc::new(Mutex::new(rng.clone())), KvStore::default(), - dummy_encode::<16, _>, + dummy_encode::, dummy_decode, ); let bindings = [(kw, vals)].into_iter(); @@ -194,7 +196,7 @@ fn bench_insert(c: &mut Criterion) { let bindings = (0..2 * n) .map(|_| { let mut a = [0; 16]; - let mut w = [0; 16]; + let mut w = [0; WORD_LENGTH]; rng.fill_bytes(&mut a); rng.fill_bytes(&mut w); (a, w) @@ -218,7 +220,7 @@ fn bench_insert(c: &mut Criterion) { seed.clone(), Arc::new(Mutex::new(rng.clone())), KvStore::default(), - dummy_encode::<16, _>, + dummy_encode::, dummy_decode, ); let bindings = (0..n) diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index 9e8ca225..13edd2ef 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -23,8 +23,8 @@ pub struct MemoryEncryptionLayer< const WORD_LENGTH: usize, Memory: MemoryADT
>, > { - k_p: SymmetricKey, - k_e: SymmetricKey, + aes: Aes256, + ae: Aes256Gcm, cch: Arc, HashMap<[u8; WORD_LENGTH], Memory::Word>>>>, rng: Arc>, mem: Memory, @@ -37,12 +37,14 @@ impl< { /// Instantiates a new memory encryption layer. pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { - let k_p = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); - let k_e = SymmetricKey::derive(&seed, &[0]).expect("secret is large enough"); + let k_p = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); + let k_e = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); + let aes = Aes256::new(GenericArray::from_slice(&k_p)); + let ae = Aes256Gcm::new(&k_e); let cch = Arc::new(Mutex::new(HashMap::new())); Self { - k_p, - k_e, + aes, + ae, cch, rng, mem: stm, @@ -104,7 +106,8 @@ impl< ad: &[u8], ) -> Result, Error> { let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); - let ctx = Aes256Gcm::new(&self.k_e) + let ctx = self + .ae .encrypt(&nonce, ptx, Some(ad)) .map_err(Error::Crypto)?; Ok([nonce.as_bytes(), &ctx].concat()) @@ -121,7 +124,8 @@ impl< } let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) .map_err(|e| Error::Parsing(e.to_string()))?; - let ptx = Aes256Gcm::new(&self.k_e) + let ptx = self + .ae .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(ad)) .map_err(Error::Crypto)?; <[u8; WORD_LENGTH]>::try_from(ptx.as_slice()).map_err(|e| Error::Conversion(e.to_string())) @@ -129,8 +133,7 @@ impl< /// Permutes the given memory address. fn permute(&self, mut a: Memory::Address) -> Memory::Address { - Aes256::new(GenericArray::from_slice(&self.k_p)) - .encrypt_block(GenericArray::from_mut_slice(&mut a)); + self.aes.encrypt_block(GenericArray::from_mut_slice(&mut a)); a } diff --git a/src/kv.rs b/src/kv.rs index 30449fa9..f37132a0 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -18,9 +18,15 @@ impl Display for MemoryError { impl std::error::Error for MemoryError {} -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct KvStore(Arc>>); +impl Default for KvStore { + fn default() -> Self { + Self(Arc::new(Mutex::new(HashMap::new()))) + } +} + impl KvStore { pub fn clear(&self) { let store = &mut *self.0.lock().expect("poisoned lock"); From af4406beeba464b3983de8fb50af44367ea2cc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 8 Jul 2024 16:29:07 +0200 Subject: [PATCH 16/42] remove unnecessary clone in vector push --- src/ovec.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ovec.rs b/src/ovec.rs index 38e12097..bad18e37 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -122,11 +122,10 @@ where // TODO: this loop will arguably terminate if the index is not highly contended, but we // need a stronger guarantee. Maybe a return with an error after a reaching a certain // number of retries. - let mut old = self.h.clone(); loop { let (cur, new) = { // Generates a new header which counter is incremented. - let mut new = old.clone().unwrap_or_default(); + let mut new = self.h.clone().unwrap_or_default(); new.cnt += vs.len() as u64; // Binds the correct addresses to the values. @@ -145,7 +144,8 @@ where .guarded_write( ( self.a.clone(), - old.clone() + self.h + .as_ref() .map(<[u8; WORD_LENGTH]>::try_from) .transpose() .map_err(|e| Error::Conversion(e))?, @@ -159,11 +159,11 @@ where (cur, new) }; - if cur.as_ref() == old.as_ref() { + if cur.as_ref() == self.h.as_ref() { self.h = Some(new); return Ok(()); } else { - old = cur; + self.h = cur; } } } @@ -200,7 +200,7 @@ where first_batch .into_iter() - .skip(1) + .skip(1) // ignore the header .chain(second_batch) .enumerate() .map(|(i, v)| v.ok_or_else(|| Error::MissingValue(self.a.clone(), i))) From 99937427eaea163f3602d1dba323ac4430ceba4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 9 Jul 2024 10:28:31 +0200 Subject: [PATCH 17/42] fixes and benches --- Cargo.toml | 1 + benches/benches.rs | 312 ++++++++++++++++++++++++++-------------- examples/insert.rs | 7 +- src/encryption_layer.rs | 21 ++- src/findex.rs | 41 +++--- src/kv.rs | 29 ++-- src/ovec.rs | 3 +- 7 files changed, 261 insertions(+), 153 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8fa5466b..1ccca651 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ futures = "0.3.30" cosmian_crypto_core = {git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/derive-clone-for-symkey", default-features = false, features = ["aes", "sha3"]} criterion = "0.5.1" tokio = {version = "1.38.0", features = ["rt", "macros", "rt-multi-thread", "time"]} +lazy_static = "1.5.0" [[bench]] name = "benches" diff --git a/benches/benches.rs b/benches/benches.rs index c037be80..85498a61 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -1,9 +1,4 @@ -#![allow(dead_code, unused)] -use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, - time::Duration, -}; +use std::{collections::HashSet, time::Duration}; use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, RngCore, SeedableRng}, @@ -11,18 +6,34 @@ use cosmian_crypto_core::{ }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; -use futures::executor::block_on; +use futures::{executor::block_on, future::join_all}; +use lazy_static::lazy_static; const WORD_LENGTH: usize = 1 + 8 * 16; -/// Builds an index that associates each `kw_i` to `10^i` values, both random 64-bit values. +lazy_static! { + static ref scale: Vec = make_scale(0, 3, 8); +} + +fn make_scale(start: usize, stop: usize, n: usize) -> Vec { + let step = ((stop - start) as f32) / n as f32; + let mut points = Vec::with_capacity(n); + for i in 0..n { + points.push(start as f32 + i as f32 * step); + } + points.push(stop as f32); + points +} + +/// Builds an index that associates each `kw_i` to x values, both random 64-bit values. fn build_benchmarking_bindings_index( rng: &mut impl CryptoRngCore, ) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { - (0..4) + scale + .iter() .map(|i| { let kw = rng.next_u64().to_be_bytes(); - let vals = (0..10_i64.pow(i) as usize) + let vals = (0..10f32.powf(*i).ceil() as usize) .map(|_| rng.next_u64().to_be_bytes()) .collect::>(); (kw, vals) @@ -43,60 +54,64 @@ fn build_benchmarking_keywords_index( .collect() } -fn bench_search(c: &mut Criterion) { +fn bench_search_multiple_bindings(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - // Bench the impact of the binding multiplicity. - { - let stm = KvStore::default(); - let index = build_benchmarking_bindings_index(&mut rng); - let findex = Findex::new( - seed.clone(), - Arc::new(Mutex::new(rng.clone())), - stm, - dummy_encode::, - dummy_decode, - ); - block_on(findex.insert(index.clone().into_iter())).unwrap(); + let stm = KvStore::default(); + let index = build_benchmarking_bindings_index(&mut rng); + let findex = Findex::new( + seed.clone(), + rng.clone(), + stm, + dummy_encode::, + dummy_decode, + ); + block_on(findex.insert(index.clone().into_iter())).unwrap(); - let mut group = c.benchmark_group("Multiple bindings search (1 keyword)"); - for (i, (kw, vals)) in index.clone().into_iter().enumerate() { - let n = 10i32.pow(i as u32) as usize; - group.bench_function(BenchmarkId::from_parameter(n), |b| { - b.iter_batched( - || [kw].into_iter(), - |kws| { - block_on(findex.search(kws)).expect("search failed"); - }, - criterion::BatchSize::SmallInput, - ); - }); - } + let mut group = c.benchmark_group("Multiple bindings search (1 keyword)"); + for (kw, vals) in index.clone().into_iter() { + group.bench_function(BenchmarkId::from_parameter(vals.len()), |b| { + b.iter_batched( + || { + findex.clear(); + [kw].into_iter() + }, + |kws| { + block_on(findex.search(kws)).expect("search failed"); + }, + criterion::BatchSize::SmallInput, + ); + }); } +} - // Bench the impact of the keyword multiplicity. +fn bench_search_multiple_keywords(c: &mut Criterion) { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + + let stm = KvStore::default(); + let index = build_benchmarking_keywords_index(&mut rng); + let findex = Findex::new( + seed, + rng, + stm.clone(), + dummy_encode::, + dummy_decode, + ); + block_on(findex.insert(index.clone().into_iter())).unwrap(); + // Reference timings { - let stm = KvStore::default(); - let index = build_benchmarking_keywords_index(&mut rng); - let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng)), - stm.clone(), - dummy_encode::, - dummy_decode, - ); - block_on(findex.insert(index.clone().into_iter())).unwrap(); - let mut group = c.benchmark_group("Multiple keywords search (1 binding)"); - for i in 0..4 { - let n = 10i32.pow(i) as usize; - group.bench_function(format!("reading {n} words from memory"), |b| { + let mut group = c.benchmark_group("retrieving words from memory"); + for i in scale.iter() { + let n = 10f32.powf(*i).ceil() as usize; + group.bench_function(BenchmarkId::from_parameter(n), |b| { // Attempts to bench all external costs (somehow, cloning the keywords impacts the // benches). b.iter_batched( || { stm.clone() .into_iter() - .map(|(a, w)| a) + .map(|(a, _)| a) .take(n) .collect::>() }, @@ -104,13 +119,20 @@ fn bench_search(c: &mut Criterion) { criterion::BatchSize::SmallInput, ); }); - // Go bench it. + } + } + // Benches + { + let mut group = c.benchmark_group("Multiple keywords search (1 binding)"); + for i in scale.iter() { + let n = 10f32.powf(*i).ceil() as usize; group.bench_function(BenchmarkId::from_parameter(n), |b| { b.iter_batched( || { + findex.clear(); // Using .cloned() instead of .clone() reduces the overhead (maybe because it // only clones what is needed) - index.iter().map(|(kw, val)| kw).take(n).cloned() + index.iter().map(|(kw, _)| kw).take(n).cloned() }, |kws| { block_on(findex.search(kws)).expect("search failed"); @@ -122,33 +144,32 @@ fn bench_search(c: &mut Criterion) { } } -fn bench_insert(c: &mut Criterion) { +fn bench_insert_multiple_bindings(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - // Bench the impact of the binding multiplicity. + let index = build_benchmarking_bindings_index(&mut rng); + let n_max = 10usize.pow(3); + + // Reference: write one word per value inserted. { - let index = build_benchmarking_bindings_index(&mut rng); - let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); - for (i, (kw, vals)) in index.clone().into_iter().enumerate() { - let n = 10i32.pow(i as u32) as usize; + let mut group = c.benchmark_group("write n words to memory"); + for (_, vals) in index.clone().into_iter() { + let stm = KvStore::with_capacity(n_max + 1); group - .bench_function(format!("inserting {n} words to memory"), |b| { + .bench_function(BenchmarkId::from_parameter(vals.len()), |b| { b.iter_batched( || { - let rng = CsRng::from_entropy(); - let seed = seed.clone(); + stm.clear(); let vals = vals.clone(); - let stm = KvStore::default(); let words = dummy_encode::(Op::Insert, vals).unwrap(); - let bindings = words + words .into_iter() .enumerate() .map(|(i, w)| ([i; 16], w)) - .collect::>(); - (stm, bindings) + .collect::>() }, - |(stm, bindings)| { + |bindings| { block_on(stm.guarded_write(([0; 16], None), bindings)) .expect("search failed"); }, @@ -156,23 +177,29 @@ fn bench_insert(c: &mut Criterion) { ); }) .measurement_time(Duration::from_secs(60)); + } + } + // Bench it + { + let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); + for (kw, vals) in index.clone().into_iter() { + let stm = KvStore::with_capacity(n_max + 1); + let findex = Findex::new( + seed.clone(), + rng.clone(), + stm.clone(), + dummy_encode::, + dummy_decode, + ); group - .bench_function(BenchmarkId::from_parameter(n), |b| { + .bench_function(BenchmarkId::from_parameter(vals.len()), |b| { b.iter_batched( || { - let seed = seed.clone(); - let vals = vals.clone(); - let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng.clone())), - KvStore::default(), - dummy_encode::, - dummy_decode, - ); - let bindings = [(kw, vals)].into_iter(); - (findex, bindings) + stm.clear(); + findex.clear(); + [(kw, vals.clone())].into_iter() }, - |(findex, bindings)| { + |bindings| { block_on(findex.insert(bindings)).expect("search failed"); }, criterion::BatchSize::SmallInput, @@ -181,19 +208,24 @@ fn bench_insert(c: &mut Criterion) { .measurement_time(Duration::from_secs(60)); } } +} - // Bench the impact of the keyword multiplicity. +fn bench_insert_multiple_keywords(c: &mut Criterion) { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + + // Reference: write one word per value inserted. { - let mut group = c.benchmark_group("Multiple keywords insert (one binding each)"); - for i in 0..4 { - let n = 10usize.pow(i); + let mut group = c.benchmark_group("write 2n words to memory"); + for i in scale.iter() { + let n = 10f32.powf(*i).ceil() as usize; + let stm = KvStore::with_capacity(2 * n); group - .bench_function(format!("inserting {n} words to memory"), |b| { + .bench_function(BenchmarkId::from_parameter(n), |b| { b.iter_batched( || { - let seed = seed.clone(); - let stm = KvStore::default(); - let bindings = (0..2 * n) + stm.clear(); + (0..2 * n) .map(|_| { let mut a = [0; 16]; let mut w = [0; WORD_LENGTH]; @@ -201,10 +233,9 @@ fn bench_insert(c: &mut Criterion) { rng.fill_bytes(&mut w); (a, w) }) - .collect::>(); - (stm, bindings) + .collect::>() }, - |(stm, bindings)| { + |bindings| { block_on(stm.guarded_write(([0; 16], None), bindings)) .expect("search failed"); }, @@ -212,28 +243,38 @@ fn bench_insert(c: &mut Criterion) { ); }) .measurement_time(Duration::from_secs(60)); + } + } + // Bench it + { + let mut group = c.benchmark_group("Multiple keywords insert (one binding each)"); + for i in scale.iter() { + let n = 10f32.powf(*i).ceil() as usize; + let stm = KvStore::with_capacity(2 * n); + let findex = Findex::new( + seed.clone(), + rng.clone(), + stm.clone(), + dummy_encode::, + dummy_decode, + ); group .bench_function(BenchmarkId::from_parameter(n), |b| { b.iter_batched( || { - let findex = Findex::new( - seed.clone(), - Arc::new(Mutex::new(rng.clone())), - KvStore::default(), - dummy_encode::, - dummy_decode, - ); - let bindings = (0..n) + stm.clear(); + findex.clear(); + (0..n) .map(|_| { ( rng.next_u64().to_be_bytes(), HashSet::from_iter([rng.next_u64().to_be_bytes()]), ) }) - .collect::>(); - (findex, bindings.into_iter()) + .collect::>() + .into_iter() }, - |(findex, bindings)| { + |bindings| { block_on(findex.insert(bindings)).expect("search failed"); }, criterion::BatchSize::SmallInput, @@ -244,10 +285,73 @@ fn bench_insert(c: &mut Criterion) { } } +fn bench_contention(c: &mut Criterion) { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + + let mut group = c.benchmark_group("Concurrent clients (single binding, same keyword)"); + for i in scale.iter() { + let n = 10f32.powf(*i).ceil() as usize; + + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(n) + .enable_all() + .build() + .unwrap(); + + let stm = KvStore::with_capacity(n + 1); + let findex = Findex::new( + seed.clone(), + rng.clone(), + stm.clone(), + dummy_encode::, + dummy_decode, + ); + + group + .bench_function(BenchmarkId::from_parameter(n), |b| { + b.iter_batched( + || { + stm.clear(); + findex.clear(); + let instances = (0..n).map(|_| findex.clone()).collect::>(); + let bindings = (0..n) + .map(|_| { + ( + rng.next_u64().to_be_bytes(), + HashSet::from_iter([rng.next_u64().to_be_bytes()]), + ) + }) + .collect::>(); + instances.into_iter().zip(bindings) + }, + |iterator| { + runtime.block_on(async { + let handles = iterator + .map(|(findex, binding)| { + tokio::spawn(async move { + findex.insert([binding].into_iter()).await + }) + }) + .collect::>(); + for res in join_all(handles).await { + res.unwrap().unwrap() + } + }) + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + } +} + criterion_group!( name = benches; config = Criterion::default().sample_size(5000); - targets = bench_search, bench_insert, + targets = bench_search_multiple_bindings, bench_search_multiple_keywords, + bench_insert_multiple_bindings, bench_insert_multiple_keywords, + bench_contention, ); criterion_main!(benches); diff --git a/examples/insert.rs b/examples/insert.rs index 21882d1e..40a3f99e 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashSet, - sync::{Arc, Mutex}, -}; +use std::collections::HashSet; use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, SeedableRng}, @@ -28,7 +25,7 @@ fn main() { let seed = Secret::random(&mut rng); let findex = Findex::new( seed, - Arc::new(Mutex::new(rng)), + rng, KvStore::default(), dummy_encode::<16, _>, dummy_decode, diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index 13edd2ef..6d1bdaaa 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -1,5 +1,5 @@ use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, ops::DerefMut, sync::{Arc, Mutex, MutexGuard}, }; @@ -36,10 +36,11 @@ impl< > MemoryEncryptionLayer { /// Instantiates a new memory encryption layer. - pub fn new(seed: Secret, rng: Arc>, stm: Memory) -> Self { + pub fn new(seed: Secret, rng: CsRng, stm: Memory) -> Self { let k_p = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); let k_e = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); let aes = Aes256::new(GenericArray::from_slice(&k_p)); + let rng = Arc::new(Mutex::new(rng)); let ae = Aes256Gcm::new(&k_e); let cch = Arc::new(Mutex::new(HashMap::new())); Self { @@ -57,12 +58,8 @@ impl< } /// Retains values cached for the given keys only. - pub fn retain_cached_keys(&self, keys: &HashSet) { - self.cch - .lock() - .expect("poisoned mutex") - .deref_mut() - .retain(|k, _| keys.contains(k)); + pub fn clear(&self) { + self.cch.lock().expect("poisoned mutex").deref_mut().clear() } /// Decrypts the given value and caches the ciphertext. @@ -197,7 +194,7 @@ impl< bindings .into_iter() .zip(tokens) - .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt_and_bind(ctx, &tok)).transpose()) + .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(&ctx, &tok)).transpose()) .collect() } @@ -230,8 +227,6 @@ impl< #[cfg(test)] mod tests { - use std::sync::{Arc, Mutex}; - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; use futures::executor::block_on; @@ -249,7 +244,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv); let tok = Address::::random(&mut rng); let ptx = [1; WORD_LENGTH]; let ctx = obf.encrypt(&ptx, &tok).unwrap(); @@ -266,7 +261,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv); + let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv); let header_addr = Address::::random(&mut rng); diff --git a/src/findex.rs b/src/findex.rs index a4402a7a..24c951bd 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -11,6 +11,7 @@ use crate::{ ovec::IVec, Address, IndexADT, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH, }; +#[derive(Clone, Debug)] pub struct Findex< const WORD_LENGTH: usize, Value, @@ -22,20 +23,22 @@ pub struct Findex< Memory::Error: Send + Sync, { el: MemoryEncryptionLayer, - vectors: Mutex< - HashMap< - Address, - IVec>, + vectors: Arc< + Mutex< + HashMap< + Address, + IVec>, + >, >, >, - encode: Box< + encode: Arc< fn( Op, HashSet, ) -> Result as MemoryADT>::Word>, String>, >, - decode: Box< + decode: Arc< fn( Vec< as MemoryADT>::Word>, ) -> Result, TryFromError>, @@ -56,7 +59,7 @@ where /// Instantiates Findex with the given seed, and memory. pub fn new( seed: Secret, - rng: Arc>, + rng: CsRng, mem: Memory, encode: fn(Op, HashSet) -> Result, String>, decode: fn(Vec<[u8; WORD_LENGTH]>) -> Result, TryFromError>, @@ -66,12 +69,17 @@ where // waiting for the lock => bench it. Self { el: MemoryEncryptionLayer::new(seed, rng, mem), - vectors: Mutex::new(HashMap::new()), - encode: Box::new(encode), - decode: Box::new(decode), + vectors: Arc::new(Mutex::new(HashMap::new())), + encode: Arc::new(encode), + decode: Arc::new(decode), } } + pub fn clear(&self) { + self.vectors.lock().unwrap().clear(); + self.el.clear(); + } + /// Caches this vector for this address. fn bind( &self, @@ -211,10 +219,7 @@ where #[cfg(test)] mod tests { - use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, - }; + use std::collections::{HashMap, HashSet}; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; use futures::executor::block_on; @@ -233,13 +238,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let findex = Findex::new( - seed, - Arc::new(Mutex::new(rng)), - kv, - dummy_encode::, - dummy_decode, - ); + let findex = Findex::new(seed, rng, kv, dummy_encode::, dummy_decode); let bindings = HashMap::<&str, HashSet>::from_iter([ ( "cat", diff --git a/src/kv.rs b/src/kv.rs index f37132a0..4faf0067 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -19,18 +19,27 @@ impl Display for MemoryError { impl std::error::Error for MemoryError {} #[derive(Clone, Debug)] -pub struct KvStore(Arc>>); +pub struct KvStore { + inner: Arc>>, +} impl Default for KvStore { fn default() -> Self { - Self(Arc::new(Mutex::new(HashMap::new()))) + Self { + inner: Arc::new(Mutex::new(HashMap::new())), + } } } impl KvStore { + pub fn with_capacity(c: usize) -> Self { + Self { + inner: Arc::new(Mutex::new(HashMap::with_capacity(c))), + } + } + pub fn clear(&self) { - let store = &mut *self.0.lock().expect("poisoned lock"); - store.clear() + self.inner.lock().expect("poisoned lock").clear(); } } @@ -44,8 +53,8 @@ impl) -> Result>, Self::Error> { - let store = &mut *self.0.lock().expect("poisoned lock"); - Ok(a.into_iter().map(|k| store.get(&k).cloned()).collect()) + let store = self.inner.lock().expect("poisoned lock"); + Ok(a.iter().map(|k| store.get(k).cloned()).collect()) } async fn guarded_write( @@ -53,7 +62,7 @@ impl), bindings: Vec<(Self::Address, Self::Word)>, ) -> Result, Self::Error> { - let store = &mut *self.0.lock().expect("poisoned lock"); + let store = &mut *self.inner.lock().expect("poisoned lock"); let (a, old) = guard; let cur = store.get(&a).cloned(); if old == cur { @@ -74,7 +83,11 @@ impl IntoIterator type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.0.lock().expect("poisoned lock").clone().into_iter() + self.inner + .lock() + .expect("poisoned lock") + .clone() + .into_iter() } } diff --git a/src/ovec.rs b/src/ovec.rs index bad18e37..ea2a275b 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -219,7 +219,6 @@ mod tests { ADDRESS_LENGTH, }; use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; - use std::sync::{Arc, Mutex}; const WORD_LENGTH: usize = 16; @@ -228,7 +227,7 @@ mod tests { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, Arc::new(Mutex::new(rng.clone())), kv.clone()); + let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv.clone()); let address = Address::random(&mut rng); let v = IVec::::new(address.clone(), obf); test_vector_sequential(&v).await; From 1e04d9451617bc1a55c0bfd5e3a92ee15a4b9a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 9 Jul 2024 10:33:25 +0200 Subject: [PATCH 18/42] upgrade CryptoCore --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ccca651..2773dcf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,10 @@ bench = [] [dependencies] aes = "0.8.4" -cosmian_crypto_core = {git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/derive-clone-for-symkey", default-features = false, features = ["aes", "sha3"]} +cosmian_crypto_core = {version = "9.5", default-features = false, features = ["aes", "sha3"]} [dev-dependencies] futures = "0.3.30" -cosmian_crypto_core = {git = "https://github.com/Cosmian/crypto_core.git", branch = "tbz/derive-clone-for-symkey", default-features = false, features = ["aes", "sha3"]} criterion = "0.5.1" tokio = {version = "1.38.0", features = ["rt", "macros", "rt-multi-thread", "time"]} lazy_static = "1.5.0" From 4b2b26f5ca69e110586d6687ec71f80f2a4916db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 9 Jul 2024 10:44:45 +0200 Subject: [PATCH 19/42] fix bench scale --- benches/benches.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 85498a61..8d682b8a 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -12,16 +12,15 @@ use lazy_static::lazy_static; const WORD_LENGTH: usize = 1 + 8 * 16; lazy_static! { - static ref scale: Vec = make_scale(0, 3, 8); + static ref scale: Vec = make_scale(0, 3, 12); } fn make_scale(start: usize, stop: usize, n: usize) -> Vec { let step = ((stop - start) as f32) / n as f32; let mut points = Vec::with_capacity(n); - for i in 0..n { + for i in 0..=n { points.push(start as f32 + i as f32 * step); } - points.push(stop as f32); points } From 4cb749684ffa3c776bb7404f99e6d7b963791369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Tue, 9 Jul 2024 15:35:35 +0200 Subject: [PATCH 20/42] rename crate and increase concurrent bench scale --- Cargo.toml | 2 +- benches/benches.rs | 37 +++++++++++++++---------------------- examples/insert.rs | 2 +- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2773dcf4..c3f58b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "findex-bis" +name = "findex" version = "0.1.0" edition = "2021" diff --git a/benches/benches.rs b/benches/benches.rs index 8d682b8a..dd75c82a 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -5,14 +5,14 @@ use cosmian_crypto_core::{ CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; use futures::{executor::block_on, future::join_all}; use lazy_static::lazy_static; const WORD_LENGTH: usize = 1 + 8 * 16; lazy_static! { - static ref scale: Vec = make_scale(0, 3, 12); + static ref scale: Vec = make_scale(0, 4, 20); } fn make_scale(start: usize, stop: usize, n: usize) -> Vec { @@ -287,17 +287,9 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { fn bench_contention(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let mut group = c.benchmark_group("Concurrent clients (single binding, same keyword)"); for i in scale.iter() { let n = 10f32.powf(*i).ceil() as usize; - - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(n) - .enable_all() - .build() - .unwrap(); - let stm = KvStore::with_capacity(n + 1); let findex = Findex::new( seed.clone(), @@ -306,6 +298,11 @@ fn bench_contention(c: &mut Criterion) { dummy_encode::, dummy_decode, ); + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(n) + .enable_all() + .build() + .unwrap(); group .bench_function(BenchmarkId::from_parameter(n), |b| { @@ -326,16 +323,12 @@ fn bench_contention(c: &mut Criterion) { }, |iterator| { runtime.block_on(async { - let handles = iterator - .map(|(findex, binding)| { - tokio::spawn(async move { - findex.insert([binding].into_iter()).await - }) - }) - .collect::>(); - for res in join_all(handles).await { - res.unwrap().unwrap() - } + join_all(iterator.map(|(findex, binding)| { + tokio::spawn( + async move { findex.insert([binding].into_iter()).await }, + ) + })) + .await }) }, criterion::BatchSize::SmallInput, @@ -348,9 +341,9 @@ fn bench_contention(c: &mut Criterion) { criterion_group!( name = benches; config = Criterion::default().sample_size(5000); - targets = bench_search_multiple_bindings, bench_search_multiple_keywords, + targets = bench_contention, + bench_search_multiple_bindings, bench_search_multiple_keywords, bench_insert_multiple_bindings, bench_insert_multiple_keywords, - bench_contention, ); criterion_main!(benches); diff --git a/examples/insert.rs b/examples/insert.rs index 40a3f99e..069e7c52 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -4,7 +4,7 @@ use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, SeedableRng}, CsRng, Secret, }; -use findex_bis::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore}; use futures::executor::block_on; fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { From f1e25ea947e1625edbad3855612dc541a94eac46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 17:35:13 +0200 Subject: [PATCH 21/42] WIP small fixes --- examples/insert.rs | 6 +++--- src/kv.rs | 1 + src/lib.rs | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/insert.rs b/examples/insert.rs index 069e7c52..a4c6215c 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -4,15 +4,15 @@ use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, SeedableRng}, CsRng, Secret, }; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, Value}; use futures::executor::block_on; -fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet<[u8; 8]>)> { +fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet)> { (0..6) .map(|i| { let kw = rng.next_u64().to_be_bytes(); let vals = (0..10_i64.pow(i) as usize) - .map(|_| rng.next_u64().to_be_bytes()) + .map(|_| Value::from(rng.next_u64() as usize)) .collect::>(); (kw, vals) }) diff --git a/src/kv.rs b/src/kv.rs index 4faf0067..19c51bda 100644 --- a/src/kv.rs +++ b/src/kv.rs @@ -32,6 +32,7 @@ impl Default for KvStore< } impl KvStore { + #[cfg(feature = "bench")] pub fn with_capacity(c: usize) -> Self { Self { inner: Arc::new(Mutex::new(HashMap::with_capacity(c))), diff --git a/src/lib.rs b/src/lib.rs index 58679497..ad979006 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,9 @@ pub use kv::KvStore; /// mitigated by marking n bit of the addresses, allowing to statistically store up to 2^((64-n)/2) /// keywords, and reducing the number of words that can be used to store associated values to /// sqrt(2^64 - 2^n). +#[cfg(not(feature = "small"))] +pub const ADDRESS_LENGTH: usize = 16; +#[cfg(feature = "small")] pub const ADDRESS_LENGTH: usize = 16; /// Using 32-byte cryptographic keys allow achieving post-quantum resistance if the adequate From 107453ade002834483b09bf9ec2fccfd89c21e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 17:42:52 +0200 Subject: [PATCH 22/42] use AES XTS --- Cargo.toml | 1 + benches/benches.rs | 9 +- examples/insert.rs | 1 - src/address.rs | 4 +- src/encoding.rs | 5 +- src/encryption_layer.rs | 177 ++++++++++++++++------------------------ src/findex.rs | 23 ++++-- src/lib.rs | 4 +- src/ovec.rs | 4 +- 9 files changed, 96 insertions(+), 132 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c3f58b9a..5ea90558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ bench = [] [dependencies] aes = "0.8.4" cosmian_crypto_core = {version = "9.5", default-features = false, features = ["aes", "sha3"]} +xts-mode = "0.5.1" [dev-dependencies] futures = "0.3.30" diff --git a/benches/benches.rs b/benches/benches.rs index dd75c82a..752673d0 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -5,12 +5,10 @@ use cosmian_crypto_core::{ CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op, WORD_LENGTH}; use futures::{executor::block_on, future::join_all}; use lazy_static::lazy_static; -const WORD_LENGTH: usize = 1 + 8 * 16; - lazy_static! { static ref scale: Vec = make_scale(0, 4, 20); } @@ -60,7 +58,6 @@ fn bench_search_multiple_bindings(c: &mut Criterion) { let index = build_benchmarking_bindings_index(&mut rng); let findex = Findex::new( seed.clone(), - rng.clone(), stm, dummy_encode::, dummy_decode, @@ -92,7 +89,6 @@ fn bench_search_multiple_keywords(c: &mut Criterion) { let index = build_benchmarking_keywords_index(&mut rng); let findex = Findex::new( seed, - rng, stm.clone(), dummy_encode::, dummy_decode, @@ -185,7 +181,6 @@ fn bench_insert_multiple_bindings(c: &mut Criterion) { let stm = KvStore::with_capacity(n_max + 1); let findex = Findex::new( seed.clone(), - rng.clone(), stm.clone(), dummy_encode::, dummy_decode, @@ -252,7 +247,6 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { let stm = KvStore::with_capacity(2 * n); let findex = Findex::new( seed.clone(), - rng.clone(), stm.clone(), dummy_encode::, dummy_decode, @@ -293,7 +287,6 @@ fn bench_contention(c: &mut Criterion) { let stm = KvStore::with_capacity(n + 1); let findex = Findex::new( seed.clone(), - rng.clone(), stm.clone(), dummy_encode::, dummy_decode, diff --git a/examples/insert.rs b/examples/insert.rs index a4c6215c..1a835375 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -25,7 +25,6 @@ fn main() { let seed = Secret::random(&mut rng); let findex = Findex::new( seed, - rng, KvStore::default(), dummy_encode::<16, _>, dummy_decode, diff --git a/src/address.rs b/src/address.rs index 51e39e2c..7a7bcbce 100644 --- a/src/address.rs +++ b/src/address.rs @@ -7,7 +7,7 @@ use cosmian_crypto_core::reexport::rand_core::CryptoRngCore; pub struct Address([u8; LENGTH]); impl Deref for Address { - type Target = [u8]; + type Target = [u8; LENGTH]; fn deref(&self) -> &Self::Target { &self.0 @@ -29,7 +29,7 @@ impl Default for Address { impl Address { pub fn random(rng: &mut impl CryptoRngCore) -> Self { let mut res = Self([0; LENGTH]); - rng.fill_bytes(&mut res); + rng.fill_bytes(&mut *res); res } } diff --git a/src/encoding.rs b/src/encoding.rs index 6462badd..d9cce075 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -17,6 +17,9 @@ pub enum Mode { Offset(usize), } +#[cfg(feature = "bench")] +pub use one_byte_metadata_uid_optimized::*; + mod one_byte_metadata_uid_optimized { //! This module defines a compact (only one systematic metadata byte) encoding that can be used //! to serialize up to two different operations. It defines two *modes*: @@ -59,7 +62,7 @@ mod one_byte_metadata_uid_optimized { /// The chunk length is the size of the available space in a word. const CHUNK_LENGTH: usize = 8 * BLOCK_LENGTH; - const WORD_LENGTH: usize = 1 + CHUNK_LENGTH; + pub const WORD_LENGTH: usize = 1 + CHUNK_LENGTH; pub(crate) fn encode>>(_op: Op, vs: HashSet) -> Vec> { let mut serialized_values = vs.into_iter().map(Into::into).collect::>(); diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index 6d1bdaaa..f0afeb04 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, ops::DerefMut, - sync::{Arc, Mutex, MutexGuard}, + sync::{Arc, Mutex}, }; use crate::{address::Address, error::Error, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH}; @@ -9,10 +9,8 @@ use aes::{ cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, Aes256, }; -use cosmian_crypto_core::{ - Aes256Gcm, CsRng, Dem, FixedSizeCBytes, Instantiable, Nonce, RandomFixedSizeCBytes, Secret, - SymmetricKey, -}; +use cosmian_crypto_core::{Secret, SymmetricKey}; +use xts_mode::Xts128; /// The encryption layers is built on top of an encrypted memory implementing the `MemoryADT` and /// exposes a plaintext virtual memory interface implementing the `MemoryADT`. @@ -24,39 +22,30 @@ pub struct MemoryEncryptionLayer< Memory: MemoryADT
>, > { aes: Aes256, - ae: Aes256Gcm, + k_e: SymmetricKey, cch: Arc, HashMap<[u8; WORD_LENGTH], Memory::Word>>>>, - rng: Arc>, mem: Memory, } impl< const WORD_LENGTH: usize, - Memory: Send + Sync + MemoryADT
, Word = Vec>, + Memory: Send + Sync + MemoryADT
, Word = [u8; WORD_LENGTH]>, > MemoryEncryptionLayer { /// Instantiates a new memory encryption layer. - pub fn new(seed: Secret, rng: CsRng, stm: Memory) -> Self { - let k_p = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); + pub fn new(seed: Secret, stm: Memory) -> Self { + let k_p = SymmetricKey::<32>::derive(&seed, &[0]).expect("secret is large enough"); let k_e = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); let aes = Aes256::new(GenericArray::from_slice(&k_p)); - let rng = Arc::new(Mutex::new(rng)); - let ae = Aes256Gcm::new(&k_e); let cch = Arc::new(Mutex::new(HashMap::new())); Self { aes, - ae, + k_e, cch, - rng, mem: stm, } } - #[inline(always)] - fn rng(&self) -> MutexGuard { - self.rng.lock().expect("poisoned lock") - } - /// Retains values cached for the given keys only. pub fn clear(&self) { self.cch.lock().expect("poisoned mutex").deref_mut().clear() @@ -65,72 +54,53 @@ impl< /// Decrypts the given value and caches the ciphertext. fn find_or_encrypt( &self, - ptx: &[u8; WORD_LENGTH], - tok: &Memory::Address, - ) -> Result, Error> { + ptx: ::Word, + tok: Memory::Address, + ) -> Memory::Word { let mut cache = self.cch.lock().expect("poisoned lock"); - if let Some(bindings) = cache.get_mut(tok) { - let ctx = bindings.get(ptx).cloned(); + if let Some(bindings) = cache.get_mut(&tok) { + let ctx = bindings.get(&ptx).cloned(); if let Some(ctx) = ctx { - Ok(ctx) + ctx } else { - let ctx = self.encrypt(ptx, tok)?; - bindings.insert(*ptx, ctx.clone()); - Ok(ctx) + let ctx = self.encrypt(ptx, *tok); + bindings.insert(ptx, ctx); + ctx } } else { // token is not marked drop(cache); - self.encrypt(ptx, tok) + self.encrypt(ptx, *tok) } } /// Decrypts the given value and caches the ciphertext. - fn decrypt_and_bind( - &self, - ctx: Vec, - tok: &Memory::Address, - ) -> Result<[u8; WORD_LENGTH], Error> { - let ptx = self.decrypt(&ctx, tok)?; - self.bind(tok, ptx, ctx); - Ok(ptx) + fn decrypt_and_bind(&self, ctx: [u8; WORD_LENGTH], tok: Memory::Address) -> [u8; WORD_LENGTH] { + let ptx = self.decrypt(ctx, *tok); + self.bind(&tok, ptx, ctx); + ptx } /// Encrypts this plaintext under this associated data - fn encrypt( - &self, - ptx: &[u8], - ad: &[u8], - ) -> Result, Error> { - let nonce = Nonce::<{ Aes256Gcm::NONCE_LENGTH }>::new(&mut *self.rng()); - let ctx = self - .ae - .encrypt(&nonce, ptx, Some(ad)) - .map_err(Error::Crypto)?; - Ok([nonce.as_bytes(), &ctx].concat()) + fn encrypt(&self, mut ptx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { + let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); + let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); + Xts128::new(cipher_1, cipher_2).encrypt_sector(&mut ptx, tok); + ptx } /// Decrypts this ciphertext under this associated data. - fn decrypt( - &self, - ctx: &[u8], - ad: &[u8], - ) -> Result<[u8; WORD_LENGTH], Error> { - if ctx.len() < Aes256Gcm::NONCE_LENGTH { - return Err(Error::Parsing("ciphertext too small".to_string())); - } - let nonce = Nonce::try_from_slice(&ctx[..Aes256Gcm::NONCE_LENGTH]) - .map_err(|e| Error::Parsing(e.to_string()))?; - let ptx = self - .ae - .decrypt(&nonce, &ctx[Aes256Gcm::NONCE_LENGTH..], Some(ad)) - .map_err(Error::Crypto)?; - <[u8; WORD_LENGTH]>::try_from(ptx.as_slice()).map_err(|e| Error::Conversion(e.to_string())) + fn decrypt(&self, mut ctx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { + let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); + let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); + Xts128::new(cipher_1, cipher_2).decrypt_sector(&mut ctx, tok); + ctx } /// Permutes the given memory address. fn permute(&self, mut a: Memory::Address) -> Memory::Address { - self.aes.encrypt_block(GenericArray::from_mut_slice(&mut a)); + self.aes + .encrypt_block(GenericArray::from_mut_slice(&mut *a)); a } @@ -154,7 +124,7 @@ impl< if let Some(bindings) = cache.get(tok) { // This token is marked. if let Some(ctx) = bindings.get(ptx) { - return Ok(Some(ctx.clone())); + return Ok(Some(*ctx)); } return Err(Error::CorruptedMemoryCache); } @@ -168,15 +138,7 @@ impl< impl< const WORD_LENGTH: usize, - // NOTE: base-memory-word length cannot be typed since "generic parameters may not be - // used in const operations". What we would have wanted is this: - // ``` - // Memory: MemoryADT< - // Address = Address, - // Word = [u8; WORD_LENGTH + Aes256Gcm::MAC_LENGTH + Aes256Gcm::NONCE_LENGTH], - // >, - // ``` - Memory: Send + Sync + MemoryADT
, Word = Vec>, + Memory: Send + Sync + MemoryADT
, Word = [u8; WORD_LENGTH]>, > MemoryADT for MemoryEncryptionLayer { type Address = Address; @@ -191,11 +153,11 @@ impl< ) -> Result>, Self::Error> { let tokens = addresses.into_iter().map(|a| self.permute(a)).collect(); let bindings = self.mem.batch_read(Vec::clone(&tokens)).await?; - bindings + Ok(bindings .into_iter() .zip(tokens) - .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(&ctx, &tok)).transpose()) - .collect() + .map(|(ctx, tok)| ctx.map(|ctx| self.decrypt(ctx, *tok))) + .collect()) } async fn guarded_write( @@ -210,17 +172,12 @@ impl< .into_iter() .map(|(a, v)| { let tok = self.permute(a); - self.find_or_encrypt(&v, &tok).map(|ctx| (tok, ctx)) + let ctx = self.find_or_encrypt(v, tok.clone()); + (tok, ctx) }) - .collect::>()?; - let cur = self - .mem - .guarded_write((tok.clone(), old.clone()), bindings) - .await?; - let res = cur - .clone() - .map(|ctx| self.decrypt_and_bind(ctx, &tok)) - .transpose()?; + .collect(); + let cur = self.mem.guarded_write((tok.clone(), old), bindings).await?; + let res = cur.map(|ctx| self.decrypt_and_bind(ctx, tok)); Ok(res) } } @@ -237,18 +194,18 @@ mod tests { MemoryADT, }; - const WORD_LENGTH: usize = 1; + const WORD_LENGTH: usize = 128; #[test] fn test_encrypt_decrypt() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv); + let kv = KvStore::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, kv); let tok = Address::::random(&mut rng); let ptx = [1; WORD_LENGTH]; - let ctx = obf.encrypt(&ptx, &tok).unwrap(); - let res = obf.decrypt(&ctx, &tok).unwrap(); + let ctx = obf.encrypt(ptx, *tok); + let res = obf.decrypt(ctx, *tok); assert_eq!(ptx.len(), res.len()); assert_eq!(ptx, res); } @@ -260,8 +217,8 @@ mod tests { fn test_vector_push() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv); + let kv = KvStore::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, kv); let header_addr = Address::::random(&mut rng); @@ -274,9 +231,9 @@ mod tests { block_on(obf.guarded_write( (header_addr.clone(), None), vec![ - (header_addr.clone(), [2]), - (val_addr_1.clone(), [1]), - (val_addr_2.clone(), [1]) + (header_addr.clone(), [2; WORD_LENGTH]), + (val_addr_1.clone(), [1; WORD_LENGTH]), + (val_addr_2.clone(), [1; WORD_LENGTH]) ] )) .unwrap(), @@ -287,30 +244,36 @@ mod tests { block_on(obf.guarded_write( (header_addr.clone(), None), vec![ - (header_addr.clone(), [2]), - (val_addr_1.clone(), [3]), - (val_addr_2.clone(), [3]) + (header_addr.clone(), [2; WORD_LENGTH]), + (val_addr_1.clone(), [3; WORD_LENGTH]), + (val_addr_2.clone(), [3; WORD_LENGTH]) ] )) .unwrap(), - Some([2]) + Some([2; WORD_LENGTH]) ); assert_eq!( block_on(obf.guarded_write( - (header_addr.clone(), Some([2])), + (header_addr.clone(), Some([2; WORD_LENGTH])), vec![ - (header_addr.clone(), [4]), - (val_addr_3.clone(), [2]), - (val_addr_4.clone(), [2]) + (header_addr.clone(), [4; WORD_LENGTH]), + (val_addr_3.clone(), [2; WORD_LENGTH]), + (val_addr_4.clone(), [2; WORD_LENGTH]) ] )) .unwrap(), - Some([2]) + Some([2; WORD_LENGTH]) ); assert_eq!( - vec![Some([4]), Some([1]), Some([1]), Some([2]), Some([2])], + vec![ + Some([4; WORD_LENGTH]), + Some([1; WORD_LENGTH]), + Some([1; WORD_LENGTH]), + Some([2; WORD_LENGTH]), + Some([2; WORD_LENGTH]) + ], block_on(obf.batch_read(vec![ header_addr, val_addr_1, diff --git a/src/findex.rs b/src/findex.rs index 24c951bd..af2d6409 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -4,7 +4,7 @@ use std::{ sync::{Arc, Mutex}, }; -use cosmian_crypto_core::{kdf128, CsRng, Secret}; +use cosmian_crypto_core::{kdf128, Secret}; use crate::{ adt::VectorADT, encoding::Op, encryption_layer::MemoryEncryptionLayer, error::Error, @@ -16,7 +16,7 @@ pub struct Findex< const WORD_LENGTH: usize, Value, TryFromError: std::error::Error, - Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, + Memory: Send + Sync + Clone + MemoryADT
, Word = [u8; WORD_LENGTH]>, > where // values are serializable (but do not depend on `serde`) for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, @@ -49,7 +49,10 @@ impl< const WORD_LENGTH: usize, Value: Send + Sync + Hash + Eq, TryFromError: std::error::Error, - Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, + Memory: Send + + Sync + + Clone + + MemoryADT
, Word = [u8; WORD_LENGTH]>, > Findex where for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, @@ -59,7 +62,6 @@ where /// Instantiates Findex with the given seed, and memory. pub fn new( seed: Secret, - rng: CsRng, mem: Memory, encode: fn(Op, HashSet) -> Result, String>, decode: fn(Vec<[u8; WORD_LENGTH]>) -> Result, TryFromError>, @@ -68,7 +70,7 @@ where // Creating many instances of Findex would need more work but potentially involve less // waiting for the lock => bench it. Self { - el: MemoryEncryptionLayer::new(seed, rng, mem), + el: MemoryEncryptionLayer::new(seed, mem), vectors: Arc::new(Mutex::new(HashMap::new())), encode: Arc::new(encode), decode: Arc::new(decode), @@ -106,7 +108,7 @@ where fn hash_address(bytes: &[u8]) -> Address { let mut a = Address::::default(); - kdf128!(&mut a, bytes); + kdf128!(&mut *a, bytes); a } @@ -170,7 +172,10 @@ impl< Keyword: Send + Sync + Hash + PartialEq + Eq + AsRef<[u8]>, Value: Send + Sync + Hash + PartialEq + Eq, TryFromError: std::error::Error, - Memory: Send + Sync + Clone + MemoryADT
, Word = Vec>, + Memory: Send + + Sync + + Clone + + MemoryADT
, Word = [u8; WORD_LENGTH]>, > IndexADT for Findex where for<'z> Value: TryFrom<&'z [u8], Error = TryFromError> + AsRef<[u8]>, @@ -237,8 +242,8 @@ mod tests { fn test_insert_search_delete_search() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let findex = Findex::new(seed, rng, kv, dummy_encode::, dummy_decode); + let kv = KvStore::, [u8; WORD_LENGTH]>::default(); + let findex = Findex::new(seed, kv, dummy_encode::, dummy_decode); let bindings = HashMap::<&str, HashSet>::from_iter([ ( "cat", diff --git a/src/lib.rs b/src/lib.rs index ad979006..91df19bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ pub use value::Value; #[cfg(any(test, feature = "bench"))] mod kv; #[cfg(feature = "bench")] -pub use encoding::{dummy_decode, dummy_encode, Op}; +pub use encoding::{dummy_decode, dummy_encode, Op, WORD_LENGTH}; #[cfg(feature = "bench")] pub use kv::KvStore; @@ -37,4 +37,4 @@ pub const ADDRESS_LENGTH: usize = 16; /// Using 32-byte cryptographic keys allow achieving post-quantum resistance if the adequate /// primitives are used (e.g. AES). -pub const KEY_LENGTH: usize = 32; +pub const KEY_LENGTH: usize = 64; diff --git a/src/ovec.rs b/src/ovec.rs index ea2a275b..56dd2688 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -226,8 +226,8 @@ mod tests { async fn test_ovec() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, Vec>::default(); - let obf = MemoryEncryptionLayer::new(seed, rng.clone(), kv.clone()); + let kv = KvStore::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, kv.clone()); let address = Address::random(&mut rng); let v = IVec::::new(address.clone(), obf); test_vector_sequential(&v).await; From a16a068a4b18a94bae18d91846c5fd09308bce43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 17:43:05 +0200 Subject: [PATCH 23/42] remove EL caches --- src/encryption_layer.rs | 91 +++-------------------------------------- src/findex.rs | 1 - 2 files changed, 6 insertions(+), 86 deletions(-) diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index f0afeb04..aa25d61f 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -1,9 +1,3 @@ -use std::{ - collections::HashMap, - ops::DerefMut, - sync::{Arc, Mutex}, -}; - use crate::{address::Address, error::Error, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH}; use aes::{ cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, @@ -23,7 +17,6 @@ pub struct MemoryEncryptionLayer< > { aes: Aes256, k_e: SymmetricKey, - cch: Arc, HashMap<[u8; WORD_LENGTH], Memory::Word>>>>, mem: Memory, } @@ -37,51 +30,10 @@ impl< let k_p = SymmetricKey::<32>::derive(&seed, &[0]).expect("secret is large enough"); let k_e = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); let aes = Aes256::new(GenericArray::from_slice(&k_p)); - let cch = Arc::new(Mutex::new(HashMap::new())); - Self { - aes, - k_e, - cch, - mem: stm, - } - } - - /// Retains values cached for the given keys only. - pub fn clear(&self) { - self.cch.lock().expect("poisoned mutex").deref_mut().clear() + Self { aes, k_e, mem: stm } } - /// Decrypts the given value and caches the ciphertext. - fn find_or_encrypt( - &self, - ptx: ::Word, - tok: Memory::Address, - ) -> Memory::Word { - let mut cache = self.cch.lock().expect("poisoned lock"); - if let Some(bindings) = cache.get_mut(&tok) { - let ctx = bindings.get(&ptx).cloned(); - if let Some(ctx) = ctx { - ctx - } else { - let ctx = self.encrypt(ptx, *tok); - bindings.insert(ptx, ctx); - ctx - } - } else { - // token is not marked - drop(cache); - self.encrypt(ptx, *tok) - } - } - - /// Decrypts the given value and caches the ciphertext. - fn decrypt_and_bind(&self, ctx: [u8; WORD_LENGTH], tok: Memory::Address) -> [u8; WORD_LENGTH] { - let ptx = self.decrypt(ctx, *tok); - self.bind(&tok, ptx, ctx); - ptx - } - - /// Encrypts this plaintext under this associated data + /// Encrypts this plaintext using its encrypted memory address as tweak. fn encrypt(&self, mut ptx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); @@ -89,7 +41,7 @@ impl< ptx } - /// Decrypts this ciphertext under this associated data. + /// Decrypts this ciphertext using its encrypted memory address as tweak. fn decrypt(&self, mut ctx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); @@ -103,37 +55,6 @@ impl< .encrypt_block(GenericArray::from_mut_slice(&mut *a)); a } - - /// Binds the given (tok, ptx, ctx) triple iff this token is marked. - fn bind(&self, tok: &Memory::Address, ptx: [u8; WORD_LENGTH], ctx: Memory::Word) { - let mut cache = self.cch.lock().expect("poisoned lock"); - if let Some(bindings) = cache.get_mut(tok) { - bindings.insert(ptx, ctx); - } - } - - /// Retrieves the ciphertext bound to the given (tok, ptx) couple. - /// Marks the token for later binding if it does not belong to any binding. - fn find_or_mark( - &self, - tok: &Memory::Address, - ptx: &Option<[u8; WORD_LENGTH]>, - ) -> Result, ::Error> { - let mut cache = self.cch.lock().expect("poisoned lock"); - if let Some(ptx) = ptx { - if let Some(bindings) = cache.get(tok) { - // This token is marked. - if let Some(ctx) = bindings.get(ptx) { - return Ok(Some(*ctx)); - } - return Err(Error::CorruptedMemoryCache); - } - } - // marking a token consists in binding it to an empty map that can later be used to - // store ctx/ptx bindings. - cache.entry(tok.clone()).or_default(); - Ok(None) - } } impl< @@ -167,17 +88,17 @@ impl< ) -> Result, Self::Error> { let (a, v) = guard; let tok = self.permute(a); - let old = self.find_or_mark(&tok, &v)?; + let old = v.map(|v| self.encrypt(v, *tok)); let bindings = bindings .into_iter() .map(|(a, v)| { let tok = self.permute(a); - let ctx = self.find_or_encrypt(v, tok.clone()); + let ctx = self.encrypt(v, *tok); (tok, ctx) }) .collect(); let cur = self.mem.guarded_write((tok.clone(), old), bindings).await?; - let res = cur.map(|ctx| self.decrypt_and_bind(ctx, tok)); + let res = cur.map(|ctx| self.decrypt(ctx, *tok)); Ok(res) } } diff --git a/src/findex.rs b/src/findex.rs index af2d6409..aa398987 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -79,7 +79,6 @@ where pub fn clear(&self) { self.vectors.lock().unwrap().clear(); - self.el.clear(); } /// Caches this vector for this address. From 3c6af18fa4e749daab590845c7d12e022030192c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 17:57:22 +0200 Subject: [PATCH 24/42] fix concurrent benchmark --- benches/benches.rs | 154 ++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 45 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 752673d0..842f8ad0 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -279,55 +279,119 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { } fn bench_contention(c: &mut Criterion) { + const N_BINDINGS: usize = 100; + const N_CLIENTS: usize = 8; let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let mut group = c.benchmark_group("Concurrent clients (single binding, same keyword)"); - for i in scale.iter() { - let n = 10f32.powf(*i).ceil() as usize; - let stm = KvStore::with_capacity(n + 1); - let findex = Findex::new( - seed.clone(), - stm.clone(), - dummy_encode::, - dummy_decode, - ); - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(n) - .enable_all() - .build() - .unwrap(); + let kws = (0..N_CLIENTS) + .map(|_| rng.next_u64().to_be_bytes()) + .collect::>(); - group - .bench_function(BenchmarkId::from_parameter(n), |b| { - b.iter_batched( - || { - stm.clear(); - findex.clear(); - let instances = (0..n).map(|_| findex.clone()).collect::>(); - let bindings = (0..n) - .map(|_| { - ( - rng.next_u64().to_be_bytes(), - HashSet::from_iter([rng.next_u64().to_be_bytes()]), - ) + // Reference: parallel clients. + { + let mut group = + c.benchmark_group("Parallel clients ({N_BINDINGS} binding, different keywords)"); + for i in 1..=N_CLIENTS { + let stm = KvStore::with_capacity(N_BINDINGS * i + 1); + let findex = Findex::new( + seed.clone(), + stm.clone(), + dummy_encode::, + dummy_decode, + ); + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(i) + .enable_all() + .build() + .unwrap(); + + let instances = (0..i).map(|_| findex.clone()).collect::>(); + let bindings = kws + .clone() + .into_iter() + .map(|kw| { + ( + kw, // All clients use a different keyword. + HashSet::from_iter((0..N_BINDINGS).map(|_| rng.next_u64().to_be_bytes())), + ) + }) + .collect::>(); + + group + .bench_function(BenchmarkId::from_parameter(i), |b| { + b.iter_batched( + || { + stm.clear(); + findex.clear(); + instances.clone().into_iter().zip(bindings.clone()) + }, + |iterator| { + runtime.block_on(async { + join_all(iterator.map(|(findex, binding)| { + tokio::spawn(async move { + findex.insert([binding].into_iter()).await + }) + })) + .await }) - .collect::>(); - instances.into_iter().zip(bindings) - }, - |iterator| { - runtime.block_on(async { - join_all(iterator.map(|(findex, binding)| { - tokio::spawn( - async move { findex.insert([binding].into_iter()).await }, - ) - })) - .await - }) - }, - criterion::BatchSize::SmallInput, - ); - }) - .measurement_time(Duration::from_secs(60)); + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + } + } + + // Concurrent clients. + { + let mut group = c.benchmark_group("Concurrent clients (single binding, same keyword)"); + for i in 1..=N_CLIENTS { + let stm = KvStore::with_capacity(N_BINDINGS * i + 1); + let findex = Findex::new( + seed.clone(), + stm.clone(), + dummy_encode::, + dummy_decode, + ); + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(i) + .enable_all() + .build() + .unwrap(); + + let instances = (0..i).map(|_| findex.clone()).collect::>(); + let bindings = (0..i) + .map(|_| { + ( + kws[0], // All clients use the same keyword + HashSet::from_iter((0..N_BINDINGS).map(|_| rng.next_u64().to_be_bytes())), + ) + }) + .collect::>(); + + group + .bench_function(BenchmarkId::from_parameter(i), |b| { + b.iter_batched( + || { + stm.clear(); + findex.clear(); + instances.clone().into_iter().zip(bindings.clone()) + }, + |iterator| { + runtime.block_on(async { + join_all(iterator.map(|(findex, binding)| { + tokio::spawn(async move { + findex.insert([binding].into_iter()).await + }) + })) + .await + }) + }, + criterion::BatchSize::SmallInput, + ); + }) + .measurement_time(Duration::from_secs(60)); + } } } From 8acfa60aae67d35574dbc42cd78d169005489d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 18:06:23 +0200 Subject: [PATCH 25/42] add permutation test --- src/encryption_layer.rs | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index aa25d61f..c1f33b64 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -16,7 +16,7 @@ pub struct MemoryEncryptionLayer< Memory: MemoryADT
>, > { aes: Aes256, - k_e: SymmetricKey, + k_e: SymmetricKey, // `Xts128` does not implement Clone mem: Memory, } @@ -33,6 +33,13 @@ impl< Self { aes, k_e, mem: stm } } + /// Permutes the given memory address. + fn permute(&self, mut a: Memory::Address) -> Memory::Address { + self.aes + .encrypt_block(GenericArray::from_mut_slice(&mut *a)); + a + } + /// Encrypts this plaintext using its encrypted memory address as tweak. fn encrypt(&self, mut ptx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); @@ -48,13 +55,6 @@ impl< Xts128::new(cipher_1, cipher_2).decrypt_sector(&mut ctx, tok); ctx } - - /// Permutes the given memory address. - fn permute(&self, mut a: Memory::Address) -> Memory::Address { - self.aes - .encrypt_block(GenericArray::from_mut_slice(&mut *a)); - a - } } impl< @@ -105,7 +105,11 @@ impl< #[cfg(test)] mod tests { - use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret}; + use aes::{ + cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit}, + Aes256, + }; + use cosmian_crypto_core::{reexport::rand_core::SeedableRng, CsRng, Secret, SymmetricKey}; use futures::executor::block_on; use crate::{ @@ -117,6 +121,21 @@ mod tests { const WORD_LENGTH: usize = 128; + #[test] + fn test_address_permutation() { + let mut rng = CsRng::from_entropy(); + let seed = Secret::random(&mut rng); + let k_p = SymmetricKey::<32>::derive(&seed, &[0]).expect("secret is large enough"); + let aes = Aes256::new(GenericArray::from_slice(&k_p)); + let kv = KvStore::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, kv); + let a = Address::::random(&mut rng); + let mut tok = obf.permute(a.clone()); + assert_ne!(a, tok); + aes.decrypt_block(GenericArray::from_mut_slice(&mut *tok)); + assert_eq!(a, tok); + } + #[test] fn test_encrypt_decrypt() { let mut rng = CsRng::from_entropy(); From 0bef16a36874adb7e2be4583c8d85fa2f758ad53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 10 Jul 2024 23:05:18 +0200 Subject: [PATCH 26/42] remove clones in encryption layer --- Cargo.toml | 4 ++-- examples/insert.rs | 5 +++-- src/encryption_layer.rs | 34 ++++++++++++++++++++++++++-------- src/findex.rs | 27 ++++++++++++--------------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ea90558..1d088774 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ cosmian_crypto_core = {version = "9.5", default-features = false, features = ["a xts-mode = "0.5.1" [dev-dependencies] -futures = "0.3.30" criterion = "0.5.1" -tokio = {version = "1.38.0", features = ["rt", "macros", "rt-multi-thread", "time"]} +futures = "0.3.30" lazy_static = "1.5.0" +tokio = {version = "1.38.0", features = ["rt", "macros", "rt-multi-thread", "time"]} [[bench]] name = "benches" diff --git a/examples/insert.rs b/examples/insert.rs index 1a835375..0ed68601 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -29,6 +29,7 @@ fn main() { dummy_encode::<16, _>, dummy_decode, ); - block_on(findex.insert(index.clone().into_iter())).expect("insert failed"); - block_on(findex.search(vec![index[5].0; 1000].into_iter())).expect("search failed"); + let kw = index[1].0; + block_on(findex.insert(index.into_iter())).expect("insert failed"); + block_on(findex.search(vec![kw; 10000].into_iter())).expect("search failed"); } diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index c1f33b64..7ed2cc9f 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -1,3 +1,5 @@ +use std::{fmt::Debug, ops::Deref, sync::Arc}; + use crate::{address::Address, error::Error, MemoryADT, ADDRESS_LENGTH, KEY_LENGTH}; use aes::{ cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, @@ -6,6 +8,23 @@ use aes::{ use cosmian_crypto_core::{Secret, SymmetricKey}; use xts_mode::Xts128; +#[derive(Clone)] +struct ClonableXts(Arc>); + +impl Debug for ClonableXts { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ClonableXts").finish() + } +} + +impl Deref for ClonableXts { + type Target = Xts128; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// The encryption layers is built on top of an encrypted memory implementing the `MemoryADT` and /// exposes a plaintext virtual memory interface implementing the `MemoryADT`. /// @@ -16,7 +35,7 @@ pub struct MemoryEncryptionLayer< Memory: MemoryADT
>, > { aes: Aes256, - k_e: SymmetricKey, // `Xts128` does not implement Clone + xts: ClonableXts, mem: Memory, } @@ -30,7 +49,10 @@ impl< let k_p = SymmetricKey::<32>::derive(&seed, &[0]).expect("secret is large enough"); let k_e = SymmetricKey::::derive(&seed, &[0]).expect("secret is large enough"); let aes = Aes256::new(GenericArray::from_slice(&k_p)); - Self { aes, k_e, mem: stm } + let aes_e1 = Aes256::new(GenericArray::from_slice(&k_e[..32])); + let aes_e2 = Aes256::new(GenericArray::from_slice(&k_e[32..])); + let xts = ClonableXts(Arc::new(Xts128::new(aes_e1, aes_e2))); + Self { aes, xts, mem: stm } } /// Permutes the given memory address. @@ -42,17 +64,13 @@ impl< /// Encrypts this plaintext using its encrypted memory address as tweak. fn encrypt(&self, mut ptx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { - let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); - let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); - Xts128::new(cipher_1, cipher_2).encrypt_sector(&mut ptx, tok); + self.xts.encrypt_sector(&mut ptx, tok); ptx } /// Decrypts this ciphertext using its encrypted memory address as tweak. fn decrypt(&self, mut ctx: [u8; WORD_LENGTH], tok: [u8; ADDRESS_LENGTH]) -> [u8; WORD_LENGTH] { - let cipher_1 = Aes256::new(GenericArray::from_slice(&self.k_e[..32])); - let cipher_2 = Aes256::new(GenericArray::from_slice(&self.k_e[32..])); - Xts128::new(cipher_1, cipher_2).decrypt_sector(&mut ctx, tok); + self.xts.decrypt_sector(&mut ctx, tok); ctx } } diff --git a/src/findex.rs b/src/findex.rs index aa398987..e35abf25 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -23,7 +23,7 @@ pub struct Findex< Memory::Error: Send + Sync, { el: MemoryEncryptionLayer, - vectors: Arc< + cache: Arc< Mutex< HashMap< Address, @@ -66,19 +66,16 @@ where encode: fn(Op, HashSet) -> Result, String>, decode: fn(Vec<[u8; WORD_LENGTH]>) -> Result, TryFromError>, ) -> Self { - // TODO: should the RNG be instantiated here? - // Creating many instances of Findex would need more work but potentially involve less - // waiting for the lock => bench it. Self { el: MemoryEncryptionLayer::new(seed, mem), - vectors: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(Mutex::new(HashMap::new())), encode: Arc::new(encode), decode: Arc::new(decode), } } pub fn clear(&self) { - self.vectors.lock().unwrap().clear(); + self.cache.lock().unwrap().clear(); } /// Caches this vector for this address. @@ -87,7 +84,7 @@ where address: Address, vector: IVec>, ) { - self.vectors + self.cache .lock() .expect("poisoned mutex") .insert(address, vector); @@ -98,7 +95,7 @@ where &self, address: &Address, ) -> Option>> { - self.vectors + self.cache .lock() .expect("poisoned mutex") .get(address) @@ -122,7 +119,7 @@ where let bindings = bindings .map(|(kw, vals)| (self.encode)(op, vals).map(|words| (kw, words))) .collect::, String>>() - .map_err(|e| Error::::Conversion(e.to_string()))?; + .map_err(|e| Error::<_, Memory::Error>::Conversion(e.to_string()))?; let futures = bindings .into_iter() @@ -143,11 +140,11 @@ where values: Vec<[u8; WORD_LENGTH]>, ) -> Result<(), >::Error> { let a = Self::hash_address(kw.as_ref()); - let mut vector = self + let mut ivec = self .find(&a) .unwrap_or_else(|| IVec::new(a.clone(), self.el.clone())); - vector.push(values).await?; - self.bind(a, vector); + ivec.push(values).await?; + self.bind(a, ivec); Ok(()) } @@ -193,7 +190,7 @@ where let futures = keywords .map(|kw| self.read::(kw)) .collect::>(); - let mut bindings = HashMap::new(); + let mut bindings = HashMap::with_capacity(futures.len()); for fut in futures { let (kw, vals) = fut.await?; bindings.insert( @@ -210,14 +207,14 @@ where &self, bindings: impl Sync + Send + Iterator)>, ) -> Result<(), Self::Error> { - self.push(Op::Insert, bindings.into_iter()).await + self.push(Op::Insert, bindings).await } async fn delete( &self, bindings: impl Sync + Send + Iterator)>, ) -> Result<(), Self::Error> { - self.push(Op::Delete, bindings.into_iter()).await + self.push(Op::Delete, bindings).await } } From b74b6b633d3285844bc850c5a8f01d4ca7078420 Mon Sep 17 00:00:00 2001 From: Chloe Hebant Date: Mon, 15 Jul 2024 17:58:05 +0200 Subject: [PATCH 27/42] review --- benches/benches.rs | 18 +++++++++--------- examples/insert.rs | 4 ++-- src/adt.rs | 5 ++--- src/encoding.rs | 4 ++-- src/encryption_layer.rs | 16 ++++++++-------- src/findex.rs | 6 +++--- src/{kv.rs => in_memory_store.rs} | 24 ++++++++++++------------ src/lib.rs | 7 +++---- src/ovec.rs | 12 ++++++------ 9 files changed, 47 insertions(+), 49 deletions(-) rename src/{kv.rs => in_memory_store.rs} (78%) diff --git a/benches/benches.rs b/benches/benches.rs index 842f8ad0..1ba0aa32 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -5,7 +5,7 @@ use cosmian_crypto_core::{ CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, MemoryADT, Op, WORD_LENGTH}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, InMemory, MemoryADT, Op, WORD_LENGTH}; use futures::{executor::block_on, future::join_all}; use lazy_static::lazy_static; @@ -54,7 +54,7 @@ fn build_benchmarking_keywords_index( fn bench_search_multiple_bindings(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let stm = KvStore::default(); + let stm = InMemory::default(); let index = build_benchmarking_bindings_index(&mut rng); let findex = Findex::new( seed.clone(), @@ -85,7 +85,7 @@ fn bench_search_multiple_keywords(c: &mut Criterion) { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let stm = KvStore::default(); + let stm = InMemory::default(); let index = build_benchmarking_keywords_index(&mut rng); let findex = Findex::new( seed, @@ -150,7 +150,7 @@ fn bench_insert_multiple_bindings(c: &mut Criterion) { { let mut group = c.benchmark_group("write n words to memory"); for (_, vals) in index.clone().into_iter() { - let stm = KvStore::with_capacity(n_max + 1); + let stm = InMemory::with_capacity(n_max + 1); group .bench_function(BenchmarkId::from_parameter(vals.len()), |b| { b.iter_batched( @@ -178,7 +178,7 @@ fn bench_insert_multiple_bindings(c: &mut Criterion) { { let mut group = c.benchmark_group("Multiple bindings insert (same keyword)"); for (kw, vals) in index.clone().into_iter() { - let stm = KvStore::with_capacity(n_max + 1); + let stm = InMemory::with_capacity(n_max + 1); let findex = Findex::new( seed.clone(), stm.clone(), @@ -213,7 +213,7 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { let mut group = c.benchmark_group("write 2n words to memory"); for i in scale.iter() { let n = 10f32.powf(*i).ceil() as usize; - let stm = KvStore::with_capacity(2 * n); + let stm = InMemory::with_capacity(2 * n); group .bench_function(BenchmarkId::from_parameter(n), |b| { b.iter_batched( @@ -244,7 +244,7 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { let mut group = c.benchmark_group("Multiple keywords insert (one binding each)"); for i in scale.iter() { let n = 10f32.powf(*i).ceil() as usize; - let stm = KvStore::with_capacity(2 * n); + let stm = InMemory::with_capacity(2 * n); let findex = Findex::new( seed.clone(), stm.clone(), @@ -292,7 +292,7 @@ fn bench_contention(c: &mut Criterion) { let mut group = c.benchmark_group("Parallel clients ({N_BINDINGS} binding, different keywords)"); for i in 1..=N_CLIENTS { - let stm = KvStore::with_capacity(N_BINDINGS * i + 1); + let stm = InMemory::with_capacity(N_BINDINGS * i + 1); let findex = Findex::new( seed.clone(), stm.clone(), @@ -346,7 +346,7 @@ fn bench_contention(c: &mut Criterion) { { let mut group = c.benchmark_group("Concurrent clients (single binding, same keyword)"); for i in 1..=N_CLIENTS { - let stm = KvStore::with_capacity(N_BINDINGS * i + 1); + let stm = InMemory::with_capacity(N_BINDINGS * i + 1); let findex = Findex::new( seed.clone(), stm.clone(), diff --git a/examples/insert.rs b/examples/insert.rs index 0ed68601..d35891ce 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -4,7 +4,7 @@ use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, SeedableRng}, CsRng, Secret, }; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, KvStore, Value}; +use findex::{dummy_decode, dummy_encode, Findex, IndexADT, InMemory, Value}; use futures::executor::block_on; fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet)> { @@ -25,7 +25,7 @@ fn main() { let seed = Secret::random(&mut rng); let findex = Findex::new( seed, - KvStore::default(), + InMemory::default(), dummy_encode::<16, _>, dummy_decode, ); diff --git a/src/adt.rs b/src/adt.rs index bef20a25..fa69fbe9 100644 --- a/src/adt.rs +++ b/src/adt.rs @@ -12,8 +12,7 @@ use std::{ }; /// An index stores *bindings*, that associate a keyword with a value. All values bound to the same -/// keyword are said to be *indexed under* this keyword. The number of such values is called the -/// volume of a keyword. +/// keyword are said to be *indexed under* this keyword. pub trait IndexADT { type Error: Send + Sync + std::error::Error; @@ -71,7 +70,7 @@ pub trait MemoryADT { ) -> impl Send + Sync + Future>, Self::Error>>; /// Write the given words at the given addresses if the word currently stored at the guard - /// address is the one given, and returns this guard word. + /// address is the given one, and returns this guard word. fn guarded_write( &self, guard: (Self::Address, Option), diff --git a/src/encoding.rs b/src/encoding.rs index d9cce075..b2d7b6bf 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,6 +1,6 @@ //! This module defines encoding operations that are used to serialize an operation. //! Currently, the only supported operations are the insertion and deletion, but there is no -//! theorical restriction on the kind of operation that can be used. +//! theoretical restriction on the kind of operation that can be used. #![allow(dead_code)] @@ -98,7 +98,7 @@ pub fn dummy_encode>( let bytes = v.as_ref(); if WORD_LENGTH - 2 < bytes.len() { return Err(format!( - "unsufficient bytes in a word to fit a value of length {}", + "insufficient bytes in a word to fit a value of length {}", bytes.len(), )); } diff --git a/src/encryption_layer.rs b/src/encryption_layer.rs index 7ed2cc9f..9b315457 100644 --- a/src/encryption_layer.rs +++ b/src/encryption_layer.rs @@ -133,7 +133,7 @@ mod tests { use crate::{ address::Address, encryption_layer::{MemoryEncryptionLayer, ADDRESS_LENGTH}, - kv::KvStore, + in_memory_store::InMemory, MemoryADT, }; @@ -145,8 +145,8 @@ mod tests { let seed = Secret::random(&mut rng); let k_p = SymmetricKey::<32>::derive(&seed, &[0]).expect("secret is large enough"); let aes = Aes256::new(GenericArray::from_slice(&k_p)); - let kv = KvStore::, [u8; WORD_LENGTH]>::default(); - let obf = MemoryEncryptionLayer::new(seed, kv); + let memory = InMemory::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, memory); let a = Address::::random(&mut rng); let mut tok = obf.permute(a.clone()); assert_ne!(a, tok); @@ -158,8 +158,8 @@ mod tests { fn test_encrypt_decrypt() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, [u8; WORD_LENGTH]>::default(); - let obf = MemoryEncryptionLayer::new(seed, kv); + let memory = InMemory::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, memory); let tok = Address::::random(&mut rng); let ptx = [1; WORD_LENGTH]; let ctx = obf.encrypt(ptx, *tok); @@ -168,15 +168,15 @@ mod tests { assert_eq!(ptx, res); } - /// Ensures a transaction can express an vector push operation: + /// Ensures a transaction can express a vector push operation: /// - the counter is correctly incremented and all values are written; /// - using the wrong value in the guard fails the operation and returns the current value. #[test] fn test_vector_push() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, [u8; WORD_LENGTH]>::default(); - let obf = MemoryEncryptionLayer::new(seed, kv); + let memory = InMemory::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, memory); let header_addr = Address::::random(&mut rng); diff --git a/src/findex.rs b/src/findex.rs index e35abf25..1d9229b8 100644 --- a/src/findex.rs +++ b/src/findex.rs @@ -228,7 +228,7 @@ mod tests { use crate::{ address::Address, encoding::{dummy_decode, dummy_encode}, - kv::KvStore, + in_memory_store::InMemory, Findex, IndexADT, Value, ADDRESS_LENGTH, }; @@ -238,8 +238,8 @@ mod tests { fn test_insert_search_delete_search() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, [u8; WORD_LENGTH]>::default(); - let findex = Findex::new(seed, kv, dummy_encode::, dummy_decode); + let memory = InMemory::, [u8; WORD_LENGTH]>::default(); + let findex = Findex::new(seed, memory, dummy_encode::, dummy_decode); let bindings = HashMap::<&str, HashSet>::from_iter([ ( "cat", diff --git a/src/kv.rs b/src/in_memory_store.rs similarity index 78% rename from src/kv.rs rename to src/in_memory_store.rs index 19c51bda..bc489800 100644 --- a/src/kv.rs +++ b/src/in_memory_store.rs @@ -19,11 +19,11 @@ impl Display for MemoryError { impl std::error::Error for MemoryError {} #[derive(Clone, Debug)] -pub struct KvStore { +pub struct InMemory { inner: Arc>>, } -impl Default for KvStore { +impl Default for InMemory { fn default() -> Self { Self { inner: Arc::new(Mutex::new(HashMap::new())), @@ -31,7 +31,7 @@ impl Default for KvStore< } } -impl KvStore { +impl InMemory { #[cfg(feature = "bench")] pub fn with_capacity(c: usize) -> Self { Self { @@ -45,7 +45,7 @@ impl KvStore MemoryADT - for KvStore + for InMemory { type Address = Address; @@ -77,7 +77,7 @@ impl IntoIterator - for KvStore + for InMemory { type Item = (Address, Value); @@ -99,30 +99,30 @@ mod tests { use crate::MemoryADT; - use super::KvStore; + use super::InMemory; - /// Ensures a transaction can express an vector push operation: + /// Ensures a transaction can express a vector push operation: /// - the counter is correctly incremented and all values are written; /// - using the wrong value in the guard fails the operation and returns the current value. #[test] fn test_vector_push() { - let kv = KvStore::::default(); + let memory = InMemory::::default(); assert_eq!( - block_on(kv.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)])).unwrap(), + block_on(memory.guarded_write((0, None), vec![(0, 2), (1, 1), (2, 1)])).unwrap(), None ); assert_eq!( - block_on(kv.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)])).unwrap(), + block_on(memory.guarded_write((0, None), vec![(0, 4), (3, 2), (4, 2)])).unwrap(), Some(2) ); assert_eq!( - block_on(kv.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)])).unwrap(), + block_on(memory.guarded_write((0, Some(2)), vec![(0, 4), (3, 3), (4, 3)])).unwrap(), Some(2) ); assert_eq!( vec![Some(1), Some(1), Some(3), Some(3)], - block_on(kv.batch_read(vec![1, 2, 3, 4])).unwrap(), + block_on(memory.batch_read(vec![1, 2, 3, 4])).unwrap(), ) } } diff --git a/src/lib.rs b/src/lib.rs index 91df19bd..59ee8fb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,11 +15,11 @@ pub use findex::Findex; pub use value::Value; #[cfg(any(test, feature = "bench"))] -mod kv; +mod in_memory_store; #[cfg(feature = "bench")] pub use encoding::{dummy_decode, dummy_encode, Op, WORD_LENGTH}; #[cfg(feature = "bench")] -pub use kv::KvStore; +pub use in_memory_store::InMemory; /// 16-byte addresses ensure a high collision resistance that poses virtually no limitation on the /// index. @@ -35,6 +35,5 @@ pub const ADDRESS_LENGTH: usize = 16; #[cfg(feature = "small")] pub const ADDRESS_LENGTH: usize = 16; -/// Using 32-byte cryptographic keys allow achieving post-quantum resistance if the adequate -/// primitives are used (e.g. AES). +/// Using 32-byte cryptographic keys allows achieving post-quantum resistance with the AES primitive. pub const KEY_LENGTH: usize = 64; diff --git a/src/ovec.rs b/src/ovec.rs index 56dd2688..c1ce91eb 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -120,11 +120,11 @@ where // ever terminate. // // TODO: this loop will arguably terminate if the index is not highly contended, but we - // need a stronger guarantee. Maybe a return with an error after a reaching a certain + // need a stronger guarantee. Maybe a return with an error after reaching a certain // number of retries. loop { let (cur, new) = { - // Generates a new header which counter is incremented. + // Generates a new header with incremented counter. let mut new = self.h.clone().unwrap_or_default(); new.cnt += vs.len() as u64; @@ -214,7 +214,7 @@ mod tests { address::Address, adt::tests::{test_vector_concurrent, test_vector_sequential}, encryption_layer::MemoryEncryptionLayer, - kv::KvStore, + in_memory_store::InMemory, ovec::IVec, ADDRESS_LENGTH, }; @@ -226,12 +226,12 @@ mod tests { async fn test_ovec() { let mut rng = CsRng::from_entropy(); let seed = Secret::random(&mut rng); - let kv = KvStore::, [u8; WORD_LENGTH]>::default(); - let obf = MemoryEncryptionLayer::new(seed, kv.clone()); + let memory = InMemory::, [u8; WORD_LENGTH]>::default(); + let obf = MemoryEncryptionLayer::new(seed, memory.clone()); let address = Address::random(&mut rng); let v = IVec::::new(address.clone(), obf); test_vector_sequential(&v).await; - kv.clear(); + memory.clear(); test_vector_concurrent(&v).await; } } From 1b06ba04a1fa34cd9fac430cb529182e5c924970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Wed, 2 Oct 2024 17:44:00 +0200 Subject: [PATCH 28/42] fix clippy warnings --- benches/benches.rs | 2 +- examples/insert.rs | 2 +- src/lib.rs | 3 --- src/ovec.rs | 5 ++--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/benches/benches.rs b/benches/benches.rs index 1ba0aa32..8fa14aa8 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -5,7 +5,7 @@ use cosmian_crypto_core::{ CsRng, Secret, }; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, InMemory, MemoryADT, Op, WORD_LENGTH}; +use findex::{dummy_decode, dummy_encode, Findex, InMemory, IndexADT, MemoryADT, Op, WORD_LENGTH}; use futures::{executor::block_on, future::join_all}; use lazy_static::lazy_static; diff --git a/examples/insert.rs b/examples/insert.rs index d35891ce..e8670e2e 100644 --- a/examples/insert.rs +++ b/examples/insert.rs @@ -4,7 +4,7 @@ use cosmian_crypto_core::{ reexport::rand_core::{CryptoRngCore, SeedableRng}, CsRng, Secret, }; -use findex::{dummy_decode, dummy_encode, Findex, IndexADT, InMemory, Value}; +use findex::{dummy_decode, dummy_encode, Findex, InMemory, IndexADT, Value}; use futures::executor::block_on; fn build_benchmarking_index(rng: &mut impl CryptoRngCore) -> Vec<([u8; 8], HashSet)> { diff --git a/src/lib.rs b/src/lib.rs index 59ee8fb1..4776944f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,9 +30,6 @@ pub use in_memory_store::InMemory; /// mitigated by marking n bit of the addresses, allowing to statistically store up to 2^((64-n)/2) /// keywords, and reducing the number of words that can be used to store associated values to /// sqrt(2^64 - 2^n). -#[cfg(not(feature = "small"))] -pub const ADDRESS_LENGTH: usize = 16; -#[cfg(feature = "small")] pub const ADDRESS_LENGTH: usize = 16; /// Using 32-byte cryptographic keys allows achieving post-quantum resistance with the AES primitive. diff --git a/src/ovec.rs b/src/ovec.rs index c1ce91eb..d91e4796 100644 --- a/src/ovec.rs +++ b/src/ovec.rs @@ -1,9 +1,8 @@ //! This module implements a simple vector, defined as a data-structure that preserves the //! following invariant: //! -//! > I_v: the value of the counter stored at the vector address is equal to the number of values -//! stored in this vector; these values are of homogeneous type and stored in contiguous -//! memory words. +//! I_v: the value of the counter stored at the vector address is equal to the number of values +//! stored in this vector; these values are of homogeneous type and stored in contiguous memory words. //! //! This implementation is based on the assumption that an infinite array starting at the vector's //! address has been allocated, and thus stores values after the header: From d2a87b5dc7fc7a8b7f6695594bac65e338ee4753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20BR=C3=89ZOT?= Date: Mon, 7 Oct 2024 14:46:50 +0200 Subject: [PATCH 29/42] anonymize repo --- .gitignore | 1 + CHANGELOG.md | 197 - Cargo.toml | 6 +- LICENSE.md | 1 - README.md | 281 - benches/benches.rs | 20 +- datasets/first_names.txt | 19948 --------------------------- datasets/key | 1 - datasets/label | 1 - datasets/serialized_index | Bin 1142017 -> 0 bytes datasets/test_vector.txt | 246 - documentation/functional.md | 110 - documentation/glossary.md | 45 - documentation/security.md | 96 - documentation/sequence-diagrams.md | 82 - documentation/serialization.md | 62 - documentation/whitepaper.pdf | Bin 1615732 -> 0 bytes examples/insert.rs | 10 +- src/address.rs | 2 +- src/encoding.rs | 153 +- src/encryption_layer.rs | 17 +- src/error.rs | 3 - src/findex.rs | 14 +- src/lib.rs | 3 + src/ovec.rs | 7 +- src/secret.rs | 92 + src/symmetric_key.rs | 65 + 27 files changed, 212 insertions(+), 21251 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 datasets/first_names.txt delete mode 100644 datasets/key delete mode 100644 datasets/label delete mode 100644 datasets/serialized_index delete mode 100644 datasets/test_vector.txt delete mode 100644 documentation/functional.md delete mode 100644 documentation/glossary.md delete mode 100644 documentation/security.md delete mode 100644 documentation/sequence-diagrams.md delete mode 100644 documentation/serialization.md delete mode 100644 documentation/whitepaper.pdf create mode 100644 src/secret.rs create mode 100644 src/symmetric_key.rs diff --git a/.gitignore b/.gitignore index 67b87df3..d3639342 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target/ **/Cargo.lock perf* flamegraph.svg +**/*.tgz diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 5d6319bc..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,197 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -## [6.0.0] - 2023-11-21 - -### Features - -Findex v6 implements new look following the work on the Findex formalization with @chloehebant. - -In order to ease the reading, fix some vocabulary first: - -- Encrypted Dictionary (EDX): a key value store which values are of constant size; -- Encrypted Multi-Map (EMM): a key value store which values are of variable size; -- Encrypted Graph (EGX): an encrypted graph which nodes contain data and pointers to other nodes; -- Encrypted Dictionary Scheme (DX-Enc): a scheme managing an EDX; -- Encrypted Multi-Map Scheme (MM-Enc): a scheme managing an EMM; -- Encrypted Graph Scheme (GX-Enc): a scheme managing an EGX; -- tag: bytes (may be a meaningful piece of information) used to point to a value in an map (H(w) in the Entry Table) -- token: _non-meaningful_ bytes used to index a value in a map (it corresponds to the UIDs) - -Findex (as the product) is now composed of three algorithms: - -- Findex: an index interface to hide the cryptographic details of Findex Graph; -- Findex Graph: a GX-Enc scheme using a MM-Enc scheme; -- Findex Multi-Map: a MM-Enc scheme using two DX-Enc schemes. - -Two generic DX-Enc schemes are used by Findex Multi-Map: - -- Entry Table: an EDX scheme in charge of storing metadata about the chains: counter, key seed (Kwi), corresponding tag (H(w_i)); -- Chain Table: an EMM used to store the actual chain data; it's implementation is actually done using an EDX. - -**HAS BEEN DONE**: - -- [x] decide which constant to fix and which constant to use as generic (e.g. `BLOCK_LENGTH`/`LINE_LENGTH`) -- [x] add compact operation -- [x] pass all old tests -- [x] pass regression tests on database: - - [x] add regression tests inside the repo (serialize in-memory database) - - [x] change counter back to the hash chain -- [x] make findex types `Sync + Send` -- [x] add encryption scheme to manage encryption -- [x] update CryptoCore version -- [x] add `progress` callback (injection) -- [x] add CATS compatibility - -## [5.0.3] - 2023-09-18 - -### Bug Fixes - -- Relax `async_trait` requirements - -### Features - -- Support `crypto_core` v9.2.0 - -## [5.0.2] - 2023-09-07 - -### Bug Fixes - -- Remove the need of nightly toolchain (use `never` and `async-trait` crates) - -## [5.0.1] - 2023-09-01 - -### Features - -- Update crypto_core to 9.1.0 - -## [5.0.0] - 2023-07-21 - -### Features - -- Changed the Search, Upsert and Compact API from mutable to immutable -- Upsert now returns the set of new keywords added to the Entry Table - -### Bug Fixes - -- add missing `async` keyword for compact callbacks -- fix `list_removed_locations` doc - -## [4.0.3] - 2023-07-11 - -### Features - -- Use crypto_core v9.0 - -## [4.0.2] - 2023-06-30 - -### Features - -- Update crypto_core to 8 - -### Testing - -- Impl Display trait on structs used in callbacks for logging - -## [4.0.1] - 2023-06-02 - -### Bug Fixes - -- Race condition in `fetch_chains()` - -## [4.0.0] - 2023-06-01 - -### Changed - -- deletions in upsert -- format of the Chain Table (block length is now 16) -- take ownership of the data in callbacks -- allow multiple ET values in search - -### Added - -- add live compact functionality - -## [3.1.0] - 2023-03-03 - -### Added - -- add macro to implement Findex traits - -## [3.0.0] - 2023-02-27 - -### Refactor - -- [**breaking**] Move all interfaces (FFI, Wasm, pyo3) to `cloudproof_rust` repository -- use Kmac256 instead of Kmac128 -- remove inline macros - -### Testing - -- Change number encoding in non regression vectors - -## [2.1.0] - 2023-02-20 - -### Bug Fixes - -- Update wasm type of progress callback - -### Features - -- Update `ProgressResult` serialization in ffi - -## [2.0.1] - 2023-02-02 - -### Features - -- Change `progress_callback` to return `NextKeyword` and `search` to return only `Location` - -### Testing - -- Add `progress_callback` tests - -## [2.0.0] - 2023-01-13 - -### Changed - -- Add `fetch_chains_batch_size` argument to search -- in `search`, `fetch_chains` calls are now run in parallel for batches of `fetch_chains_batch_size` (not working for the Java/Flutter interfaces for now) -- Improve errors - -### Ci - -- Replace .gitlab-ci.yml with github actions -- Test inter-languages compatibility - -## [1.0.1] - 2022-12-16 - -### Bug Fixes - -- FFI: fix serialization of returned values in `fetch_entry` -- compact: replace fetch_entry loop with call to `fetch_entry_table_uids` - -## [1.0.0] - 2022-12-16 - -### Bug Fixes - -- Add return type to FindexInternal function signature - -### Documentation - -- Update findex cryptographic documentation - -### Features - -- Only implement the callbacks required by the desired features (upsert, search, compact) -- Improve callback error message - -### Miscellaneous Tasks - -- Release findex v1.0 - -### Ci - -- Add .gitlab-ci.yml - - diff --git a/Cargo.toml b/Cargo.toml index 1d088774..69059503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,12 @@ bench = [] [dependencies] aes = "0.8.4" -cosmian_crypto_core = {version = "9.5", default-features = false, features = ["aes", "sha3"]} +rand = "0.8.5" +rand_chacha = "0.3.1" +rand_core = "0.6.4" +tiny-keccak = { version = "2.0.2", features = ["sha3"] } xts-mode = "0.5.1" +zeroize = { version = "1.8.1", features = ["derive"] } [dev-dependencies] criterion = "0.5.1" diff --git a/LICENSE.md b/LICENSE.md index bbb6a739..b3750af3 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -8,7 +8,6 @@ A copy of this license is available below. # Commercial License All usages, not covered by the Open Source License, are covered by a commercial license. -Please contact Cosmian for pricing and details. # Affero GPL/v3 License diff --git a/README.md b/README.md index 272b33cb..e5cacfd2 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,5 @@ # Findex -![Build status](https://github.com/Cosmian/findex/actions/workflows/ci.yml/badge.svg) -![Build status](https://github.com/Cosmian/findex/actions/workflows/build.yml/badge.svg) -![latest version](https://img.shields.io/crates/v/cosmian_findex.svg) - -Findex aims to solve the following problem: - -> How to securely recover the _location_ of an encrypted data matching a given -> _keyword_? - -It is a cryptographic protocol designed to securely make search queries on an -untrusted cloud server. Thanks to its encrypted indexes, large databases can -securely be outsourced without compromising usability. - -Findex is part of Cosmian Cloudproof Encryption. - - - -- [Getting started](#getting-started) -- [Building and testing](#building-and-testing) -- [Findex indexes](#findex-indexes) - * [Two indexing strategies](#two-indexing-strategies) -- [Benchmarks](#benchmarks) -- [Documentation](#documentation) - - - -## Getting started - -Findex allows indexing values by keywords. These values can be locations (UIDs -of an encrypted database, URLs, paths, etc.). - -Using Findex API one can: - -- index or deindex values by keywords via the `FindexUpsert` trait; -- search for keywords via the `FindexSearch` trait; -- compact the indexes via the `FindexCompact` trait. - -These traits can be automatically implemented and a macro is provided to help -with the syntax. The default parameters (the ones used by the macro) are -defined in [`parameters.rs`](./src/parameters.rs). - -Findex delegates to the user the implementation of _callbacks_ to manipulate -the indexes. This makes Findex compatible with any database technology since no database-specific code is part of it. Implementation is done via the - -See `in_memory_example.rs` for an example of implementation. - -## Building and testing - To build Findex simply run: ```bash @@ -65,236 +17,3 @@ To launch the benchmarks, run: ```bash cargo bench --all-features ``` - -## Findex indexes - -Findex relies on two server-side indexes: - -- **Entry Table**: provides the values needed to fetch the correct locations - from the Chain Table. Each indexing keyword matches a line in the Entry - Table. -- **Chain Table**: securely stores the indexed values. These indexed values may - be locations or pointers to other keywords. Locations usually are database - UIDs, but Findex can be used to index any kind of location (URL, path...). In - order to make lines indistinguishable, the variable length indexed values are - stored by blocks of fixed length and the same number of blocks is stored in - each line (padding is added where necessary). - -Findex indexes are key-value stores whose structure is given in the following -tables, with $K_{w_i}$ the ephemeral key associated with a keyword $w_i$, -$H_{w_i}$ the hash of $w_i$ and $UID_{last}$ the last UID of the chain of -indexed values associated to $w_i$. - - - - - - - - - - - - - - - -
Entry Table
keyvalue
UID$K_{w_i}$$H_{w_i}$$UID_{last}$
- - - - - - - - - - - - - - - -
Chain Table
keyvalue
UID$\textnormal{block}_1$...$\textnormal{block}_B$
- -The Chain Table values are serialized as follows (sizes are given in bytes): - - - - - - - - - - - - - - - - - - - - - - - - - -
flagBlock1...BlockB
prefixdata...prefixdata
Size (in bytes)1116...116
- -When stored, the values of the indexes are symmetrically encrypted with an -AEAD. Our implementation uses a 16-bytes MAC tag and a 12-bytes nonce. - -The flag is used to mark the blocks as being addition or deletions. Each bit -corresponds to a block, which limits the possible number of blocks inside a -single Chain Table value to 8. The prefix is used to write the actual length of -the data stored inside a block. - -Therefore: - -- given $N$ the number of keywords used, the size of the Entry Table is given - by (in bytes): - -```math -L_{entry~table} = (L_{uid} + C_e + L_{K_{w_i}} + L_{H_{w_i}} + L_{uid}) \cdot N - = 140 \cdot N -``` - -- given $V(w_i)$ the volume of the keyword $w_i$ (i.e. the number of values - indexed by this keyword) the size of the Chain Table is given by (in bytes): - -```math -L_{chain~table} = \left(L_{uid} + C_e + 1 + B * (1 + L_{block})\right) \sum\limits_{i~\in~[1,N]}\left\lceil \frac{V(w_i)}{B}\right\rceil - = 146 \sum\limits_{i~\in~[1;N]}\left\lceil \frac{V(w_i)}{5}\right\rceil -``` - -where: - -- the length of an UID: $L_{uid} = 32~\textnormal{bytes}$ -- the length of the ephemeral key: $K_{w_i} = 16~\textnormal{bytes}$ -- the length of the hash of the keyword: $H_{w_i} = 32~\textnormal{bytes}$ -- the Chain Table width: $B = 5$ -- the block length: $L_{block} = 16~\textnormal{bytes}$ -- the encryption overhead: $C_e = 28~\textnormal{bytes}$ - -### Two indexing strategies - -Naive (locations are indexed for all possible slices): - -- `mar` -> {locations} -- `mart` -> {locations} -- `marti` -> {locations} -- `martin` -> {locations} -- `martine` -> {locations} - -Mixed: - -- `mar` -> `martine` -- `mart` -> `martine` -- `marti` -> `martine` -- `martin` -> `martine` -- `martine` -> {locations} - -Graph: - -- `mar` -> `mart` -- `mart` -> `marti` -- `marti` -> `martin` -- `martin` -> `martine` -- `martine` -> {locations} - -More client/server interactions are needed for the graph solution: the depth of -the graph (4 in this example) compared to 1 for the naive solution and 2 for -the mixed solution. - -On the other hand, the graph solution optimizes the size of the Chain Table. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Avg locations#recordssize (in KB)ratio
naivemixedgraphnaivemixedgraphmixed / naivegraph / naive
14901653058493166988756470311.081.01
25825357347535268305817676310.980.92
371455618175794910187881382620.870.81
480692666716278511504950589510.830.78
5860487267669014122681036298390.840.80
- -## Benchmarks - -The benchmarks presented in this section are run on an Intel(R) Xeon(R) Platinum 8171M CPU @ 2.60GHz. - -- [Findex in memory (no database)](./benches/BENCHMARKS.md) - -## Documentation - -Findex supporting paper can be found the Findex -[whitepaper](./documentation/whitepaper.pdf). - -The developer documentation can be found on [doc.rs](https://docs.rs/cosmian_findex/latest/cosmian_findex/index.html) diff --git a/benches/benches.rs b/benches/benches.rs index 8fa14aa8..cb8a428d 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -1,13 +1,13 @@ use std::{collections::HashSet, time::Duration}; -use cosmian_crypto_core::{ - reexport::rand_core::{CryptoRngCore, RngCore, SeedableRng}, - CsRng, Secret, -}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use findex::{dummy_decode, dummy_encode, Findex, InMemory, IndexADT, MemoryADT, Op, WORD_LENGTH}; +use findex::{ + dummy_decode, dummy_encode, Findex, InMemory, IndexADT, MemoryADT, Op, Secret, WORD_LENGTH, +}; use futures::{executor::block_on, future::join_all}; use lazy_static::lazy_static; +use rand_chacha::ChaChaRng; +use rand_core::{CryptoRngCore, RngCore, SeedableRng}; lazy_static! { static ref scale: Vec = make_scale(0, 4, 20); @@ -52,7 +52,7 @@ fn build_benchmarking_keywords_index( } fn bench_search_multiple_bindings(c: &mut Criterion) { - let mut rng = CsRng::from_entropy(); + let mut rng = ChaChaRng::from_entropy(); let seed = Secret::random(&mut rng); let stm = InMemory::default(); let index = build_benchmarking_bindings_index(&mut rng); @@ -82,7 +82,7 @@ fn bench_search_multiple_bindings(c: &mut Criterion) { } fn bench_search_multiple_keywords(c: &mut Criterion) { - let mut rng = CsRng::from_entropy(); + let mut rng = ChaChaRng::from_entropy(); let seed = Secret::random(&mut rng); let stm = InMemory::default(); @@ -140,7 +140,7 @@ fn bench_search_multiple_keywords(c: &mut Criterion) { } fn bench_insert_multiple_bindings(c: &mut Criterion) { - let mut rng = CsRng::from_entropy(); + let mut rng = ChaChaRng::from_entropy(); let seed = Secret::random(&mut rng); let index = build_benchmarking_bindings_index(&mut rng); @@ -205,7 +205,7 @@ fn bench_insert_multiple_bindings(c: &mut Criterion) { } fn bench_insert_multiple_keywords(c: &mut Criterion) { - let mut rng = CsRng::from_entropy(); + let mut rng = ChaChaRng::from_entropy(); let seed = Secret::random(&mut rng); // Reference: write one word per value inserted. @@ -281,7 +281,7 @@ fn bench_insert_multiple_keywords(c: &mut Criterion) { fn bench_contention(c: &mut Criterion) { const N_BINDINGS: usize = 100; const N_CLIENTS: usize = 8; - let mut rng = CsRng::from_entropy(); + let mut rng = ChaChaRng::from_entropy(); let seed = Secret::random(&mut rng); let kws = (0..N_CLIENTS) .map(|_| rng.next_u64().to_be_bytes()) diff --git a/datasets/first_names.txt b/datasets/first_names.txt deleted file mode 100644 index cfd2be5d..00000000 --- a/datasets/first_names.txt +++ /dev/null @@ -1,19948 +0,0 @@ -Añaterve -Añes -Aadil -Aali -Aaliyah -Aaltje -Aamar -Aamer -Aamir -Aaron -Aase -Aatika -Aaya -Aba -Ababacar -Abad -Abarca -Abarne -Abass -Abasse -Abba -Abbas -Abbass -Abbes -Abbey -Abbie -Abby -Abdala -Abdalah -Abdalahe -Abdalahi -Abdalla -Abdallah -Abdallahi -Abdel -Abdela -Abdelaali -Abdelaati -Abdelaaziz -Abdeladim -Abdelah -Abdelakrim -Abdelali -Abdelasis -Abdelati -Abdelatif -Abdelazis -Abdelaziz -Abdelazize -Abdelbaki -Abdelbari -Abdelbasset -Abdeldjalil -Abdelelah -Abdelfatah -Abdelfattah -Abdelfetah -Abdelfettah -Abdelghafour -Abdelghani -Abdelhabib -Abdelhadi -Abdelhafid -Abdelhai -Abdelhak -Abdelhakem -Abdelhakim -Abdelhalak -Abdelhalim -Abdelhamed -Abdelhamid -Abdelhanin -Abdelhaq -Abdelhay -Abdelhila -Abdelhilah -Abdelilah -Abdelillah -Abdeljabar -Abdeljabbar -Abdeljalak -Abdeljalal -Abdeljalil -Abdeljaouad -Abdeljebbar -Abdelkabir -Abdelkader -Abdelkadir -Abdelkamal -Abdelkamel -Abdelkarim -Abdelkarin -Abdelkbir -Abdelkebir -Abdelkerim -Abdelkhalak -Abdelkhalek -Abdelkhaleq -Abdelkhalik -Abdelkrim -Abdellah -Abdellahi -Abdellali -Abdellatif -Abdellaziz -Abdellilah -Abdelmadjid -Abdelmajid -Abdelmalek -Abdelmalik -Abdelmaoula -Abdelmayid -Abdelmjid -Abdelmoghit -Abdelmonaim -Abdelmonim -Abdelmoughit -Abdelmoula -Abdelmoumen -Abdelmounaim -Abdelmounim -Abdeloihab -Abdeloihed -Abdeloihid -Abdelouadoud -Abdelouafi -Abdelouahab -Abdelouahad -Abdelouahd -Abdelouahed -Abdelouahhab -Abdelouahid -Abdelouhab -Abdelqader -Abdelrahim -Abdelrahman -Abdelrhani -Abdeltif -Abdeluahed -Abdeluahid -Abdelwahab -Abdelwahad -Abdelwahed -Abdelwahid -Abdenabi -Abdenacer -Abdenaji -Abdenasser -Abdenbi -Abdennabi -Abdennacer -Abdennaceur -Abdennaim -Abdennaji -Abdennasar -Abdennaser -Abdennassar -Abdennasser -Abdennebi -Abdennour -Abdenour -Abderahim -Abderahman -Abderahmane -Abderazak -Abderazzak -Abderezak -Abderhim -Abderrachid -Abderrafia -Abderrafie -Abderrafik -Abderrahaman -Abderrahim -Abderrahime -Abderrahman -Abderrahmane -Abderrahmen -Abderraman -Abderraouf -Abderrazak -Abderrazaq -Abderrazek -Abderrazzak -Abderrazzaq -Abderrezak -Abderrezzak -Abderzak -Abdesalam -Abdesamad -Abdeselam -Abdeslam -Abdeslem -Abdessadek -Abdessadeq -Abdessadik -Abdessalam -Abdessalem -Abdessamad -Abdessamed -Abdessamie -Abdesselam -Abdesslam -Abdesslem -Abdi -Abdias -Abdiel -Abdollah -Abdon -Abdona -Abdou -Abdoul -Abdoulaye -Abdoulie -Abdoullah -Abdourahamane -Abdourahman -Abdourahmane -Abdrahamane -Abdramane -Abdu -Abdul -Abdulah -Abdulai -Abdulaziz -Abdulla -Abdullah -Abdulrahman -Abdur -Abdurrahman -Abdus -Abe -Abed -Abel -Abelard -Abelardo -Abelia -Abelina -Abelino -Abelio -Abenaura -Abenchara -Abencio -Abian -Abib -Abibatou -Abibou -Abid -Abida -Abidine -Abiel -Abigail -Abilia -Abilio -Abimael -Abir -Abisai -Abla -Ablaye -Abner -Abou -Aboubacar -Aboubakar -Aboubakary -Aboubaker -Aboubakr -Aboudou -Abraan -Abraham -Abrahan -Abrahim -Abram -Abran -Abrar -Abril -Absalon -Abselam -Abu -Abubacar -Abubacarr -Abubakar -Abubakari -Abubakary -Abubaker -Abul -Abulai -Abundia -Abundio -Aby -Acacia -Acacio -Acaimo -Acaymo -Acelia -Acelina -Acerina -Acher -Achille -Achim -Achiuta -Achoucha -Achour -Achoura -Achraf -Achucha -Acindino -Acisclo -Acoidan -Acoraida -Acoran -Acoydan -Acracia -Acracio -Ada -Adah -Adahi -Adahy -Adai -Adaia -Adair -Adal -Adalberon -Adalbert -Adalberta -Adalberto -Adalgisa -Adalgiza -Adalia -Adalid -Adaline -Adam -Adama -Adame -Adamina -Adamo -Adams -Adan -Adao -Adara -Adargoma -Adasat -Adassa -Aday -Adaya -Adda -Addaia -Addi -Addie -Adeel -Adeela -Adei -Adel -Adela -Adelaida -Adelaide -Adelaido -Adelardo -Adelayda -Adele -Adelfa -Adelfina -Adelheid -Adelia -Adelin -Adelina -Adelinda -Adeline -Adelino -Adelio -Adelita -Adell -Adella -Adelle -Adelma -Adelmo -Adelo -Adelto -Adeluta -Adem -Ademar -Ademir -Adena -Adenso -Adeodato -Aderita -Aderito -Adesuwa -Adexe -Adham -Adhara -Adhemar -Adi -Adib -Adiel -Adiela -Adil -Adila -Adilia -Adilson -Adina -Adinath -Adiran -Adirane -Adita -Aditi -Aditya -Adjona -Adjou -Adjutori -Admir -Admiracion -Adnan -Adnane -Adolf -Adolfa -Adolfina -Adolfino -Adolfo -Adolph -Adonai -Adonais -Adonay -Adonaya -Adonina -Adonino -Adonis -Adoracio -Adoracion -Adoralina -Adorinda -Adosinda -Adria -Adriaan -Adriadna -Adriam -Adrian -Adriana -Adriane -Adrianna -Adrianne -Adriano -Adrianus -Adriel -Adrien -Adriene -Adrienn -Adrienne -Adrista -Aduanich -Aduen -Adulai -Adulfo -Aduna -Adur -Afaf -Afafe -Afia -Afif -Afifa -Afnan -Afonso -Afra -Afrae -Africa -Afrika -Afrodisia -Afrodisio -Afrodita -Afshan -Aftab -Afton -Afzaal -Afzal -Agafia -Agamenon -Agapita -Agapito -Agar -Agata -Agate -Agatha -Agathe -Agatoclio -Ageda -Agenor -Ager -Aglae -Aglaia -Aglaya -Agnaldo -Agne -Agnelio -Agnes -Agnese -Agneta -Agni -Agnieszka -Agnus -Agoney -Agora -Agostina -Agostinho -Agostino -Agricio -Agripina -Agripino -Agua -Aguas -Aguasanta -Aguasantas -Agueda -Aguedo -Aguila -Aguinaldo -Agurne -Agurtxane -Agurtzane -Agusti -Agustin -Agustina -Agusto -Ahamed -Aharon -Ahcene -Ahinara -Ahinoa -Ahinoam -Ahitana -Ahitor -Ahlalia -Ahlam -Ahlame -Ahlem -Ahmad -Ahmadou -Ahmed -Ahmedou -Ahmet -Ahmida -Ahmidou -Ahren -Ahsan -Ahtisham -Ai -Aia -Aiala -Aiara -Aibin -Aicha -Aichata -Aichatou -Aichetou -Aicong -Aid -Aida -Aidan -Aidas -Aide -Aidee -Aiden -Aidi -Aie -Aiert -Aifang -Aifen -Aifeng -Aiguang -Aiguo -Aihnoa -Aihong -Aihua -Aijiao -Aiju -Aijuan -Aijun -Aikaterini -Aiko -Aila -Ailan -Aileen -Ailen -Ailene -Aili -Ailian -Ailin -Ailing -Ailton -Ailyn -Aima -Aimad -Aiman -Aimane -Aimar -Aimara -Aime -Aimee -Aimei -Aimen -Aimin -Aina -Ainara -Aine -Aines -Ainet -Aingeru -Ainhara -Ainhitze -Ainhize -Ainhoa -Ainitze -Ainize -Ainna -Ainnoa -Ainoa -Ainoha -Aintxane -Aintza -Aintzane -Aintzira -Aiora -Aiping -Aiqin -Aira -Airam -Airan -Airas -Aires -Airi -Airidas -Airin -Airis -Airon -Airong -Airton -Airy -Aisa -Aisam -Aisatou -Aisatu -Aisetu -Aisha -Aisling -Aissa -Aissam -Aissata -Aissatou -Aissatu -Aiste -Aitami -Aitana -Aitane -Aithamy -Aithana -Aithor -Aitiana -Aitor -Aitxiber -Aitz -Aitziber -Aitzol -Aitzpea -Aiub -Aiur -Aivaras -Aiwei -Aixa -Aixiang -Aixiong -Aiyan -Aiyana -Aiying -Aiyong -Aiyu -Aiyue -Aiyun -Aiza -Aizane -Aizea -Aizeti -Aizhen -Aizhu -Aizpea -Aja -Ajay -Ajaz -Ajit -Ajmal -Akaki -Akane -Akash -Akbar -Akemi -Aketx -Aketza -Akhtar -Akier -Akiko -Akil -Akila -Akilah -Akim -Akira -Akli -Akmal -Akos -Akram -Aksana -Aksel -Aksiniya -Akvile -Akwasi -Al -Ala -Alaa -Alaaeddine -Aladina -Aladino -Aladji -Alae -Alaeddine -Alagie -Alai -Alaia -Alaide -Alain -Alaina -Alaine -Alaitz -Alaksmi -Alam -Alamgir -Alami -Alan -Alana -Alane -Alanis -Alanna -Alano -Alarico -Alasana -Alasne -Alassana -Alassane -Alastair -Alatz -Alayn -Alayna -Alazne -Alba -Alban -Albana -Albania -Albano -Albar -Albarina -Albaro -Albas -Albeiro -Albena -Alber -Alberico -Albert -Alberta -Albertas -Alberte -Albertha -Albertina -Albertine -Albertino -Alberto -Albertus -Albi -Albin -Albina -Albino -Albita -Albito -Alborada -Albrecht -Alby -Alcantara -Alcazar -Alcibiades -Alcides -Alcina -Alcino -Alcione -Alcira -Alcor -Alcora -Alda -Aldair -Aldan -Aldana -Aldara -Aldegunda -Aldemar -Alden -Aldin -Aldina -Aldo -Aldona -Aldonza -Aldric -Aldrin -Ale -Alea -Alease -Alec -Alecia -Alecsandru -Alecu -Aleen -Aleena -Alegra -Alegria -Aleh -Aleida -Aleidis -Aleisha -Aleix -Aleixandra -Aleixandre -Aleixo -Aleja -Alejandra -Alejandrina -Alejandrino -Alejandro -Alejo -Alejos -Alek -Aleko -Aleks -Aleksandar -Aleksander -Aleksandr -Aleksandra -Aleksandras -Aleksandre -Aleksandrina -Aleksandrov -Aleksandrs -Aleksei -Aleksej -Aleksejs -Aleksey -Aleksi -Alen -Alena -Alene -Alenjandro -Alenka -Ales -Alesander -Alesandra -Alesandro -Alesha -Aleshia -Alesia -Alessandra -Alessandro -Alessia -Alessio -Alesya -Aleta -Aleth -Aletha -Alethea -Alethia -Aleu -Alevtina -Alex -Alexa -Alexand -Alexandar -Alexande -Alexander -Alexandr -Alexandra -Alexandre -Alexandria -Alexandrina -Alexandro -Alexandros -Alexandru -Alexe -Alexei -Alexey -Alexi -Alexia -Alexis -Alexsander -Alexsandra -Alexsandro -Alexy -Aleyda -Alf -Alfaro -Alfia -Alfie -Alfio -Alfiya -Alfons -Alfonsa -Alfonsina -Alfonso -Alfonzo -Alfred -Alfreda -Alfredia -Alfredo -Alfusainey -Algimantas -Algirdas -Algis -Alguer -Alhagi -Alhagie -Alhaji -Alharilla -Alhassan -Alhassane -Ali -Alia -Aliaksandr -Aliaksandra -Aliaksei -Alian -Aliana -Alianza -Alica -Alice -Alicia -Alicio -Alicja -Alida -Alie -Alien -Aliena -Alieu -Alik -Alim -Alima -Alimatou -Alimou -Alin -Alina -Alinda -Aline -Aliona -Aliou -Alioune -Alipio -Alireza -Alirio -Alis -Alisa -Alise -Alisea -Aliseda -Alisha -Alishba -Alishia -Alisia -Alison -Alissa -Alisson -Alistair -Alister -Alita -Aliu -Aliuska -Alix -Aliya -Aliza -Alize -Alizee -Alizia -Alla -Allah -Allal -Allan -Alle -Alleen -Allegra -Allen -Allena -Allende -Allene -Allie -Alline -Allison -Allyn -Allyson -Alma -Almamy -Almeda -Almerina -Almerinda -Almeta -Almike -Almir -Almira -Almiro -Almudena -Almut -Aloña -Aloa -Alodia -Aloe -Aloha -Aloia -Alois -Aloma -Alona -Alondra -Alonso -Alonzo -Alou -Alpha -Alphonse -Alphonso -Alphousseyni -Alpidia -Alpidio -Alseny -Alsina -Alsira -Alta -Altaf -Altagracia -Altair -Altamira -Altea -Altha -Althea -Alton -Alva -Alvar -Alvara -Alvard -Alvarina -Alvaro -Alvera -Alverta -Alvin -Alvina -Alvino -Alvydas -Aly -Alya -Alyce -Alycia -Alyona -Alyosha -Alysa -Alyse -Alysha -Alysia -Alyson -Alyssa -Alzbeta -Alzira -Ama -Amabel -Amabilia -Amabilio -Amable -Amada -Amadea -Amadeo -Amadeu -Amadina -Amado -Amador -Amadora -Amadou -Amadu -Amady -Amagoia -Amagoya -Amai -Amaia -Amaiur -Amal -Amale -Amalfi -Amalia -Amalio -Amalur -Amalya -Aman -Amanat -Amancia -Amancio -Amanda -Amandeep -Amandina -Amandine -Amandino -Amandio -Amando -Amane -Amanecer -Amani -Amante -Amantina -Amapola -Amar -Amara -Amaral -Amaranta -Amaranto -Amarfil -Amarildo -Amarilis -Amarilys -Amarjit -Amaro -Amarouch -Amaru -Amat -Amatallah -Amath -Amauri -Amaury -Amaya -Amayra -Ambar -Ambarisha -Amber -Amberly -Ambra -Ambreen -Ambrocio -Ambros -Ambrose -Ambrosia -Ambrosio -Amdy -Amed -Amedeo -Amee -Ameer -Amel -Amelia -Amelie -Amelio -Amen -Amena -Amenhotep -Amer -America -Americo -Ameth -Amets -Ametsa -Ametz -Ameur -Ami -Amia -Amidou -Amie -Amiee -Amiel -Amilcar -Amilkar -Amin -Amina -Aminata -Amine -Aminetou -Aminta -Aminul -Amir -Amira -Amiran -Amit -Amjad -Amjid -Amma -Ammar -Ammara -Ammie -Amna -Amneris -Amor -Amos -Ampar -Amparito -Amparo -Ampelio -Amr -Amra -Amran -Amrani -Amrik -Amrinder -Amrit -Amritpal -Amsatou -Amsha -Amy -An -Ana -Anabel -Anabela -Anabell -Anabella -Anabelle -Anacleta -Anacleto -Anael -Anahi -Anahid -Anahis -Anahit -Anahy -Anai -Anaid -Anaida -Anair -Anais -Anaisa -Anait -Anaitz -Anali -Analia -Analis -Analisa -Analiza -Analyn -Anam -Anamaria -Anand -Ananda -Ananias -Anara -Anaraida -Anarbella -Anartz -Anas -Anass -Anasse -Anastacia -Anastacio -Anastasi -Anastasia -Anastasiia -Anastasija -Anastasio -Anastasios -Anastasiya -Anastassia -Anatael -Anatalia -Anatole -Anatoli -Anatolia -Anatolie -Anatolii -Anatolio -Anatoliy -Anatoly -Anay -Anaya -Anayansi -Anayara -Anays -Anca -Anchel -Anchidim -Ancizar -Ancor -Ancuta -Anda -Andalucia -Andeka -Ander -Andera -Andere -Anders -Anderson -Andersson -Andi -Andima -Andion -Andoitz -Andon -Andone -Andoni -Andra -Andrada -Andranik -Andras -Andre -Andrea -Andreas -Andree -Andreea -Andrei -Andreia -Andreina -Andrej -Andrejs -Andrejus -Andres -Andresa -Andressa -Andreu -Andrew -Andrews -Andrey -Andreza -Andria -Andrian -Andriana -Andries -Andrii -Andris -Andriu -Andrius -Andriy -Andriyan -Andro -Andronico -Andros -Andry -Andrzej -Andy -Andzelika -Ane -Aneela -Anel -Anelia -Anelis -Aneliya -Aner -Anes -Anesia -Anesio -Anet -Aneta -Anett -Anette -Aneu -Aneudy -Anfal -Anfisa -Ange -Angel -Angela -Angelberto -Angele -Angelena -Angeles -Angelia -Angelic -Angelica -Angelico -Angelika -Angelina -Angeline -Angelines -Angelino -Angelique -Angelita -Angelito -Angella -Angelo -Angelov -Angelova -Angels -Angely -Angelyn -Anghara -Angharad -Anghel -Anghelina -Anghelus -Angie -Angila -Angiolina -Angla -Angle -Anglea -Angosto -Anguel -Angus -Angustia -Angustias -Angy -Anh -Anhelina -Ani -Ania -Aniana -Aniano -Anibal -Anica -Anicet -Aniceta -Aniceto -Anicia -Anicuta -Aniel -Aniela -Aniello -Anika -Aniko -Anil -Anila -Anilda -Anina -Aniol -Anir -Anira -Aniria -Anis -Anisa -Anisha -Anisia -Anisio -Anisley -Anisoara -Aniss -Anissa -Anita -Anitra -Anitz -Aniuska -Anja -Anjali -Anjana -Anjanette -Anjara -Anje -Anjela -Anjelica -Anjos -Anju -Anjum -Anka -Anke -Ankor -Anmol -Ann -Anna -Annabel -Annabell -Annabella -Annabelle -Annais -Annalee -Annalisa -Annamae -Annamaria -Annamarie -Annas -Anne -Annegret -Anneke -Anneli -Annelie -Annelies -Anneliese -Annelise -Annelle -Annemarie -Annemieke -Annet -Annett -Annetta -Annette -Anni -Annia -Annice -Annick -Annie -Annika -Annis -Annita -Annmarie -Annunziata -Anny -Anoar -Anoir -Anouar -Anouk -Anqi -Anri -Ansa -Ansar -Anselm -Anselma -Anselmo -Ansou -Ansoumana -Ansoumane -Ansumana -Anta -Antal -Antanas -Antea -Antela -Antelmo -Antenor -Antera -Antero -Anthea -Anthonia -Anthony -Antia -Antidia -Antidio -Antigona -Antigua -Antima -Antimo -Antioco -Antione -Antionette -Antje -Antoan -Antoanela -Antoaneta -Antoine -Antoinette -Antoliana -Antoliano -Antolin -Antolina -Antolino -Anton -Antone -Antonel -Antonela -Antonella -Antonello -Antoneta -Antonetta -Antonette -Antoni -Antonia -Antonica -Antonie -Antonieta -Antonietta -Antonin -Antonina -Antonino -Antonio -Antonios -Antoniu -Antonius -Antoniya -Antony -Antton -Antulio -Antwan -Antxoka -Antxon -Anuar -Anuncia -Anunciacio -Anunciacion -Anunciada -Anunciata -Anush -Anuska -Anuta -Anwar -Anxel -Anxela -Anxo -Anxos -Anya -Anyela -Anyeli -Anyelina -Anyelo -Anyi -Anzelika -Anzhela -Anzhelika -Ao -Aoife -Aomar -Aouatef -Aouatif -Aouda -Aoued -Aouicha -Aparecida -Aparicia -Aparicio -Apolinar -Apolinaria -Apolinario -Apollonia -Apolo -Apolonia -Apolonio -Apostol -Apostolos -April -Aproniano -Apryl -Apsara -Aqeel -Aqib -Aqsa -Aquiles -Aquilina -Aquilino -Aquino -Ara -Arabel -Arabela -Arabella -Arabi -Arabia -Araceli -Aracelia -Aracelis -Aracelly -Aracely -Aracelys -Aradia -Arafa -Arafat -Arageme -Aragones -Arai -Araia -Araitz -Araiz -Arale -Aram -Arama -Aramata -Arame -Aramis -Aramita -Aran -Arancha -Aranda -Arane -Aranka -Arantxa -Arantxazu -Arantza -Arantzazu -Aranxa -Aranza -Aranzazu -Ararat -Arash -Aratz -Aray -Araya -Arayik -Arben -Arbey -Arbi -Arbia -Arcadi -Arcadia -Arcadie -Arcadio -Arcangel -Arcangela -Arcelia -Arcenia -Arcenio -Arcesio -Archie -Archil -Arcilia -Ardath -Ardelia -Ardell -Ardella -Ardelle -Arden -Ardiel -Ardis -Ardith -Area -Areceli -Arecio -Areli -Arelis -Arely -Arelys -Arena -Arend -Arene -Ares -Areta -Aretha -Aretx -Arevik -Arezki -Arfan -Arfang -Argelia -Argelina -Argelio -Argeme -Argemira -Argemiro -Argenis -Argentina -Argentino -Argeo -Argiñe -Argi -Argia -Argider -Argimira -Argimiro -Argoitz -Arhane -Arhimo -Arhimou -Arhoa -Ariñe -Ari -Aria -Ariadna -Ariadne -Ariadnna -Ariam -Arian -Ariana -Ariane -Arianna -Arianne -Arianny -Arica -Aridai -Aridane -Aridani -Aridany -Ariday -Aridia -Aridian -Arie -Ariel -Ariela -Arielle -Arif -Arik -Arild -Arima -Arina -Arion -Aris -Arismendy -Aristarco -Aristeo -Aristide -Aristides -Aristobulo -Aristoteles -Aritz -Aritza -Arjan -Arjun -Arkadi -Arkadiusz -Arkadiy -Arkaitz -Arkia -Arla -Arlean -Arleen -Arlen -Arlena -Arlene -Arles -Arlet -Arlete -Arletha -Arletta -Arlette -Arlex -Arley -Arlie -Arlinda -Arlindo -Arline -Arlyn -Arlyne -Arman -Armand -Armanda -Armandas -Armandina -Armando -Armelina -Armelinda -Armelle -Armen -Armengol -Armenia -Armenio -Armenuhi -Armiche -Armida -Armide -Armin -Arminda -Armindo -Armine -Armonia -Arnaitz -Arnald -Arnaldo -Arnas -Arnau -Arnaud -Arne -Arnel -Arnelio -Arnetta -Arnette -Arnita -Arno -Arnold -Arnoldas -Arnoldo -Arnulfo -Aroa -Aroha -Aroia -Arola -Aroldo -Aroma -Aron -Arona -Arooj -Aroua -Arouna -Arpad -Arpine -Arquimedes -Arquimides -Arrate -Arrieta -Arritokieta -Arritxu -Arron -Arsalan -Arsela -Arseli -Arselina -Arsen -Arseni -Arsenia -Arsenio -Arseniy -Arshad -Arshak -Arsilia -Arslan -Art -Artai -Artak -Artashes -Artem -Artemi -Artemia -Artemio -Artemis -Artemisa -Artemiy -Artemiza -Artesino -Arthur -Artie -Artiom -Artur -Artura -Arturas -Arturo -Arturs -Artus -Artyom -Artzai -Aruca -Aruma -Arume -Arun -Aruna -Arunas -Arundhati -Arusyak -Arvid -Arvilla -Arvin -Arvydas -Arwa -Arwen -Aryaman -Aryan -Asa -Asaad -Asad -Asael -Asahel -Asan -Asbel -Ascanio -Ascencion -Ascendina -Asceneth -Ascensio -Ascension -Asdrubal -Ase -Asel -Asela -Asen -Asencio -Asenet -Asensi -Asensia -Asensio -Aser -Asghar -Asha -Ashanti -Ashely -Asher -Ashfaq -Ashiq -Ashlea -Ashlee -Ashleigh -Ashley -Ashli -Ashlie -Ashly -Ashlyn -Ashok -Ashot -Ashraf -Ashton -Ashwani -Asia -Asiel -Asier -Asif -Asifa -Asim -Asima -Asis -Asiya -Asjad -Asjid -Askoa -Aslam -Aslan -Asley -Asma -Asmaa -Asmae -Asmahan -Asmahane -Asmat -Asparuh -Assa -Assad -Assan -Assane -Asser -Assetou -Assia -Assier -Assis -Assitan -Assiya -Assma -Assmaa -Assmae -Assou -Assumpcio -Assumpta -Assuncao -Assunta -Assya -Asta -Asteria -Asterio -Astghik -Astika -Astor -Astou -Astrid -Asun -Asuncion -Asunta -Asur -Asura -Asya -Atalia -Atanas -Atanasia -Atanasio -Atanaska -Atanasov -Atasara -Ataulfo -Atef -Atena -Atenea -Athanasios -Athena -Athenea -Athina -Athmane -Atidzhe -Atif -Atifa -Atik -Atika -Atila -Atilana -Atilano -Atilio -Atiq -Atiqa -Atman -Atmane -Atocha -Atou -Atreyu -Atri -Atsegiñe -Atsuko -Atsushi -Atta -Atteneri -Attenery -Attenya -Attia -Attila -Attilio -Auba -Aubrey -Audaz -Aude -Audelia -Audelina -Audelino -Audie -Audra -Audrea -Audrey -Audria -Audrie -Audrius -Audrone -Audry -Augurio -August -Augusta -Auguste -Augustin -Augustina -Augustine -Augusto -Augustus -Auicha -Auixa -Aundrea -Aura -Aurang -Aurangzeb -Auras -Aurea -Aurel -Aureli -Aurelia -Aurelian -Aureliana -Aureliano -Aurelie -Aurelien -Aurelija -Aurelijus -Aurelina -Aurelio -Aurembiaix -Aurentina -Aureo -Auria -Aurica -Aurimas -Aurina -Auristela -Aurita -Auritz -Aurkene -Aurora -Aurore -Ausencia -Ausencio -Ausias -Auspicio -Ausra -Austin -Austine -Autumn -Auxibio -Auxiliadora -Auxilio -Ava -Ave -Avelia -Avelina -Avelinda -Avelino -Avencio -Aventina -Aventino -Avery -Avetik -Avigail -Avilia -Avilio -Avinash -Avis -Avram -Avril -Avtandil -Avtar -Awa -Awais -Awatef -Awatif -Awilda -Axel -Axelle -Axenia -Axier -Axular -Aya -Ayaan -Ayachi -Ayad -Ayada -Ayah -Ayako -Ayala -Ayan -Ayana -Ayanna -Ayara -Ayat -Ayatima -Ayax -Ayaz -Aycha -Ayda -Aydan -Ayde -Aydee -Ayeisa -Ayelen -Ayesa -Ayesha -Ayhan -Ayla -Ayleen -Aylen -Aylin -Ayman -Aymane -Aymar -Aymara -Aymee -Aymen -Ayna -Aynara -Aynhoa -Aynoa -Aynoha -Aynur -Ayob -Ayose -Ayoub -Ayoube -Ayoze -Ayram -Ayran -Ayrton -Ayse -Aysel -Aysha -Ayshe -Aytami -Aytana -Ayten -Aythami -Aythamy -Ayub -Ayuda -Ayumi -Ayyoub -Ayyub -Az -Aza -Azad -Azael -Azahar -Azahara -Azahra -Azalea -Azalee -Azam -Azan -Azara -Azarias -Azddin -Azddine -Azdin -Azdine -Azeddin -Azeddine -Azedine -Azeem -Azhar -Azibar -Aziz -Aziza -Azize -Azman -Azmat -Azouz -Azra -Azucena -Azul -Azuzena -Azza -Azzahra -Azzeddin -Azzeddine -Azzedine -Azzie -Azziz -Azzouz -B -Baños -Ba -Baba -Babacar -Babak -Babar -Babara -Babette -Babil -Babila -Babou -Baboucarr -Babucarr -Babul -Bacar -Bacary -Bachar -Bachir -Bachira -Bacilia -Bacilio -Bada -Badar -Badara -Badayco -Badea -Badel -Bader -Badi -Badia -Badiaa -Badr -Badra -Badre -Badreddin -Badreddine -Badredin -Badredine -Badri -Badria -Baerbel -Bafode -Bagdad -Baghdad -Bah -Baha -Bahadur -Bahia -Bahija -Bahiya -Bahram -Bahri -Baidy -Bailey -Bailo -Bairon -Bakar -Bakari -Bakarne -Bakartxo -Bakary -Bakasura -Bakhta -Bakr -Bala -Balal -Balam -Balarama -Balazs -Balbanera -Balbina -Balbino -Balbir -Balde -Baldev -Baldiri -Baldomer -Baldomera -Baldomero -Baldomino -Balduino -Bali -Balint -Baljeet -Baljinder -Baljit -Balkar -Balla -Balma -Balraj -Baltasar -Baltasara -Baltazar -Balvina -Balvir -Balwant -Balwinder -Bamba -Bambi -Bambo -Banda -Bandiougou -Banesa -Banessa -Bangali -Bangally -Bangaly -Banna -Banta -Banu -Bao -Baptista -Baptiste -Bara -Baraa -Barabara -Barae -Baralides -Barb -Barbar -Barbara -Barbaro -Barbel -Barbera -Barbie -Barbora -Barbra -Barbu -Barca -Barek -Bari -Barka -Barna -Barney -Baroudi -Barrett -Barrie -Barry -Bart -Bartlomiej -Bartola -Bartolina -Bartolo -Bartolome -Bartolomea -Bartomeu -Barton -Bartosz -Baruc -Baruch -Basel -Baselisa -Basem -Basharat -Bashir -Basil -Basili -Basilia -Basiliano -Basilides -Basilio -Basilisa -Basiliso -Basiru -Basit -Basma -Basmala -Bassam -Bassem -Bassim -Bassima -Bassirou -Bassma -Bassou -Bastiaan -Bastian -Bastien -Bat -Bathie -Batilde -Batirtze -Batista -Batiste -Batoul -Batoula -Battista -Batul -Baudelia -Baudelio -Baudilia -Baudilio -Baustista -Bautista -Baya -Bayan -Baye -Bayron -Beñat -Bea -Beat -Beata -Beate -Beato -Beatrice -Beatris -Beatriu -Beatrix -Beatriz -Beau -Beaulah -Beauty -Bebe -Becki -Beckie -Becky -Beda -Bee -Begoña -Begoñe -Begonia -Begonya -Begum -Bei -Beibei -Beilei -Beimar -Beinat -Beka -Bekaye -Bekkay -Bekkaye -Bel -Bela -Belahouel -Belaid -Belal -Belarmina -Belarmino -Belen -Belgacem -Belgica -Belhaj -Belia -Belina -Belinda -Belisa -Belisaria -Belisario -Belkacem -Belkassem -Belkheir -Belkhir -Belkis -Belkys -Bell -Bella -Belle -Belleda -Belmira -Belmiro -Beltran -Belva -Belvis -Bemba -Ben -Benabdellah -Benachir -Benaisa -Benaissa -Benali -Benamar -Benancio -Benaouda -Benardina -Benardino -Benat -Benayga -Bencomo -Bendaoud -Bendehiba -Bendicion -Beneamin -Benedetta -Benedetto -Benedict -Benedicta -Benedicte -Benedicto -Benedikt -Benedita -Benedito -Beneharo -Benemerito -Beneranda -Benet -Beniamin -Benicia -Benicio -Benigna -Benigno -Benilda -Benilde -Benildo -Benita -Benito -Benjami -Benjamin -Benjamina -Bennacer -Bennaceur -Bennasser -Bennett -Bennie -Benno -Benny -Benoit -Benone -Benoni -Benson -Bent -Bentchey -Bente -Bentejui -Bento -Benton -Bentor -Bentorey -Benvinguda -Benvingut -Benyahia -Benyamin -Benyounes -Benyoussef -Bera -Beralides -Berardo -Berdaitz -Berena -Berend -Berengario -Berenguela -Berenguer -Berenice -Berezi -Bergoi -Berit -Berkis -Berna -Bernabe -Bernabea -Bernabela -Bernadeta -Bernadett -Bernadette -Bernadi -Bernadine -Bernal -Bernando -Bernard -Bernarda -Bernardeta -Bernardette -Bernardi -Bernardina -Bernardine -Bernardino -Bernardita -Bernardo -Bernardus -Bernat -Bernd -Berneice -Bernetta -Bernhard -Bernice -Bernie -Berniece -Bernita -Beronica -Berry -Bert -Berta -Bertha -Berthold -Bertie -Bertila -Bertilda -Bertilia -Bertin -Bertina -Bertino -Berto -Bertol -Bertomeu -Bertram -Bertran -Bertrand -Beryl -Besay -Besik -Bess -Bessie -Bet -Betania -Beth -Bethania -Bethanie -Bethann -Bethany -Bethel -Bethsabe -Bethy -Betina -Betisa -Betlem -Betsabe -Betsaida -Betsey -Betsy -Bette -Bettie -Bettina -Betty -Bettyann -Bettye -Betuel -Bety -Betzabe -Betzaida -Beula -Beulah -Bev -Beverlee -Beverley -Beverly -Bezza -Bhaga -Bhagwan -Bhajan -Bharat -Bharata -Bharata -Bharti -Bhavna -Bhima -Bhrigu -Bhupinder -Bi -Biagio -Bianca -Bianka -Biao -Bibi -Bibian -Bibiana -Bibiano -Bidane -Bidatz -Bieito -Biel -Bienbenida -Bienvenida -Bienvenido -Bihotz -Bihotza -Bikendi -Bikram -Bikramjit -Bilal -Bilale -Bilaly -Bilel -Biljana -Bill -Billal -Billel -Billi -Billie -Billy -Billye -Bilma -Bilyana -Bin -Bina -Binbin -Bineta -Binetou -Bing -Bingbing -Bingen -Binod -Binta -Bintou -Bintu -Biotza -Birama -Birame -Birane -Birdie -Birger -Birgit -Birgitt -Birgitta -Birgitte -Birte -Birthe -Birute -Biser -Biserka -Bisila -Bisma -Bismarck -Bismark -Bistra -Bita -Bitor -Bittor -Biviana -Bixente -Bixia -Biying -Biyu -Bjarne -Bjoern -Bjorg -Bjorn -Bladimir -Bladimiro -Blagovest -Blagoy -Blai -Blaine -Blair -Blaise -Blake -Blanca -Blancanieves -Blanch -Blanche -Blandina -Blandine -Blanka -Blas -Blasa -Blasida -Blasina -Blasinda -Blau -Blay -Blessing -Blondell -Blossom -Blythe -Bo -Boarisch -Bob -Bobbi -Bobbie -Bobby -Bobbye -Bobette -Bobi -Bobo -Bocar -Bochra -Bodil -Bodo -Bogdan -Bogdana -Bogna -Bogomil -Bogumil -Bogumila -Boguslaw -Boguslawa -Bohdan -Bohdana -Boi -Bojan -Bojana -Bok -Bolena -Boleslao -Boleslaw -Bolivar -Bolivia -Bonaventura -Bondad -Bong -Boni -Bonifacia -Bonifacio -Bonifaz -Bonita -Bonka -Bonnie -Bonny -Bonosa -Bonoso -Booker -Borgia -Boris -Borislav -Borislava -Borisov -Borja -Borjas -Boryana -Borys -Bosanski -Bosco -Bose -Botaina -Bouabdallah -Bouabdellah -Bouabid -Boualam -Boualem -Bouamama -Bouarfa -Bouazza -Bouazzaoui -Bouba -Boubacar -Boubakar -Boubaker -Boubeker -Boubekeur -Boubkar -Boubker -Boubou -Bouchaib -Bouchara -Bouchra -Bouchta -Boucif -Boudali -Boufelja -Boughaleb -Boujama -Boujamaa -Boujema -Boujemaa -Boukary -Boukhayar -Boukhiar -Boulaye -Boulus -Boumedien -Boumediene -Bouna -Bounama -Bourama -Boureima -Bouselham -Bousselham -Bousso -Boutahar -Boutaher -Boutaina -Boutayeb -Boutayna -Boutros -Bouzekri -Bouzian -Bouziane -Bouzid -Bowen -Boyan -Boyana -Boyce -Boyd -Boyka -Boyko -Bozena -Bozhana -Bozhidar -Brad -Bradford -Bradley -Bradly -Brady -Brahian -Brahim -Brahima -Brahime -Brahin -Brahma -Braian -Braima -Brain -Brais -Bram -Bran -Branda -Brandan -Brande -Brandee -Branden -Brandi -Brandie -Brando -Brandon -Brandy -Branimir -Branislav -Branka -Branko -Brant -Braudilia -Braulia -Braulio -Brayan -Breana -Breann -Breanna -Breanne -Breda -Bree -Brehima -Breixo -Brenda -Brendan -Brendon -Brenna -Breno -Brent -Brenton -Breogan -Bret -Brett -Brezhoneg -Brezo -Briam -Brian -Briana -Brianda -Brianna -Brianne -Brice -Bricio -Bridget -Bridgett -Bridgette -Brigette -Bright -Brigid -Brigida -Brigido -Brigit -Brigita -Brigitta -Brigitte -Brihaspati -Brinda -Brindusa -Brisa -Briseida -Brit -Brita -Britany -Britney -Britni -Britt -Britta -Brittaney -Brittani -Brittanie -Brittany -Britteny -Brittney -Brittni -Brittny -Brock -Broderick -Bronislava -Bronislaw -Bronwyn -Brook -Brooke -Brooks -Broulaye -Bru -Bruce -Brugues -Bruna -Brunella -Brunhilde -Brunilda -Bruno -Bryam -Bryan -Bryanna -Bryant -Bryce -Brynn -Bryon -Btisam -Btissam -Btissame -Buba -Bubacar -Bubacarr -Bubakar -Bubakari -Bubakary -Buchra -Buck -Bucur -Bud -Buddy -Buen -Buena -Buenaventu -Buenaventura -Buensuceso -Buenviaje -Bueyo -Buffy -Buford -Bujor -Bula -Bulah -Bully -Bunny -Burama -Burgess -Burgo -Burkhard -Burl -Burma -Burt -Burton -Bushra -Bustar -Buster -Buzzian -Byron -C -Caños -Cañosanto -Cañosantos -Cabeza -Caetano -Cahora -Cai -Caifen -Caifeng -Caihong -Caihua -Cain -Caio -Caiping -Caiqin -Caitlin -Caitlyn -Caixia -Caiyan -Caiyun -Cala -Calamanda -Calandra -Calasanz -Caleb -Calin -Calina -Calista -Calisto -Calixta -Calixto -Callie -Callum -Calogero -Calvin -Camara -Camelia -Camellia -Camen -Cameron -Cami -Camie -Camil -Camila -Camilia -Camilla -Camille -Camillo -Camilo -Camino -Cammie -Cammy -Campio -Campo -Can -Cancio -Candace -Candance -Candela -Candelaria -Candelario -Candelas -Candelo -Candi -Candice -Candid -Candida -Candido -Candie -Candis -Candra -Candy -Candyce -Canek -Cantia -Canto -Canuta -Canuto -Capilla -Capitolina -Capitulina -Caprice -Cara -Caren -Carey -Cari -Caridad -Carie -Carim -Carima -Carime -Carin -Carina -Carine -Carisa -Carissa -Carita -Caritat -Carl -Carla -Carlee -Carleen -Carlena -Carlene -Carles -Carletta -Carley -Carli -Carlie -Carlina -Carline -Carlita -Carlito -Carlitos -Carlixta -Carlo -Carlos -Carlota -Carlotta -Carlton -Carly -Carlyn -Carma -Carman -Carme -Carmel -Carmela -Carmelia -Carmelina -Carmelita -Carmella -Carmelo -Carmem -Carmen -Carmencita -Carmenza -Carmiña -Carmina -Carminda -Carmine -Carminia -Carmita -Carmo -Carmon -Caro -Carol -Carola -Carolann -Carolay -Carole -Carolee -Carolin -Carolina -Caroline -Carolino -Caroll -Carolo -Carolyn -Carolyne -Carolynn -Caron -Caroyln -Carrasca -Carri -Carrie -Carriona -Carrodilla -Carrol -Carroll -Carry -Carson -Carsten -Carter -Cary -Caryl -Carylon -Caryn -Casandra -Casey -Casian -Casiana -Casiano -Casie -Casilda -Casildo -Casimir -Casimira -Casimiro -Casio -Casiodoro -Cassandra -Cassaundra -Cassey -Cassi -Cassia -Cassidy -Cassie -Cassio -Cassondra -Cassy -Castañar -Casta -Castellar -Castillo -Casto -Castor -Castora -Castorina -Castro -Castula -Castulo -Cataisa -Cataldo -Catalin -Catalina -Catalino -Catarina -Cataysa -Categoria -Categorias -Caterin -Caterina -Caterine -Cathaisa -Catharina -Catharine -Cathaysa -Catherin -Catherina -Catherine -Cathern -Catheryn -Cathey -Cathi -Cathie -Cathleen -Cathrine -Cathryn -Cathy -Cati -Catia -Catiana -Catina -Catinca -Catita -Catrice -Catrin -Catrina -Catuxa -Caty -Cautar -Cauzar -Caya -Cayetana -Cayetano -Cayla -Cayo -Ce -Cebria -Cebrian -Cecelia -Cecil -Cecila -Cecile -Cecilia -Cecilie -Cecilio -Cecille -Cecily -Cedric -Cedrick -Ceesay -Ceferina -Ceferino -Ceila -Cel -Celedonia -Celedonio -Celena -Celene -Celenia -Celerina -Celerino -Celesta -Celeste -Celesti -Celestin -Celestina -Celestine -Celestino -Celi -Celia -Celiano -Celida -Celina -Celinda -Celine -Celinia -Celino -Celio -Cellou -Celma -Celmira -Cels -Celsa -Celsina -Celso -Celtia -Cenaida -Ceneida -Cenelia -Cenobia -Cenobio -Ceola -Cerasela -Cerca -Ceres -Cesaltina -Cesar -Cesare -Cesarea -Cesareo -Cesaria -Cesarina -Cesarino -Cesario -Cesc -Cesia -Cesidio -Ceu -Cezar -Cezarina -Cezary -Chaabane -Chabane -Chabeli -Chabier -Chad -Chada -Chadi -Chadia -Chadli -Chadwick -Chadya -Chae -Chafi -Chafia -Chafiaa -Chafik -Chafika -Chafiq -Chahd -Chahid -Chahida -Chahinaz -Chahinez -Chahira -Chahrazad -Chahrazed -Chaia -Chaib -Chaibia -Chaima -Chaimaa -Chaimae -Chaka -Chaker -Chakib -Chakir -Chakira -Chama -Chamaida -Chan -Chana -Chance -Chanda -Chandra -Chandru -Chanel -Chanell -Chanelle -Chang -Changchun -Changqing -Changsheng -Chantal -Chantay -Chante -Chantel -Chantell -Chantelle -Chao -Chaoping -Chaouki -Chaoyang -Chara -Charaf -Charalampos -Charanjit -Charef -Charif -Charifa -Charis -Charise -Charissa -Charisse -Charita -Charito -Charity -Charkaoui -Charki -Charla -Charleen -Charlena -Charlene -Charles -Charlesetta -Charlette -Charley -Charlie -Charline -Charlott -Charlotte -Charlsie -Charly -Charlyn -Charmain -Charmaine -Charo -Charolette -Chas -Chase -Chasidy -Chasity -Chassidy -Chastity -Chau -Chaudhry -Chauncey -Chavdar -Chaxiraxi -Chaya -Chayma -Chaymaa -Chaymae -Chedey -Chegdali -Cheick -Cheickna -Cheickne -Cheik -Cheikh -Cheikhna -Cheikhou -Cheikhouna -Cheila -Cheima -Chej -Chelo -Chelsea -Chelsey -Chelsie -Chema -Chems -Chen -Chenchen -Cheng -Chengcheng -Chenghao -Chengjie -Chengjun -Chenglong -Chengyi -Chenhao -Chenoa -Chenxi -Chenyang -Chenyi -Chenyu -Cher -Chere -Cheree -Cherelle -Cheri -Cherie -Cherif -Cherifa -Cherilyn -Cherise -Cherish -Cherkaoui -Cherkaouia -Cherki -Cherly -Cherlyn -Cherno -Cherri -Cherrie -Cherry -Cherryl -Chery -Cheryl -Cheryle -Cheryll -Chester -Chesus -Chet -Cheyenne -Chi -Chia -Chiara -Chiavana -Chie -Chieko -Chifae -Chihab -Chima -Chin -China -Ching -Chiquinquira -Chiquita -Chirila -Chirstian -Chistian -Chistopher -Chivuta -Chloe -Choaib -Chokri -Chomicha -Chong -Chorouk -Chouaib -Choudhry -Choukri -Choumicha -Chourouk -Chrif -Chrifa -Chris -Chrissy -Christa -Christal -Christeen -Christel -Christele -Christelle -Christen -Christena -Christene -Christi -Christia -Christiaan -Christiam -Christian -Christiana -Christiane -Christie -Christin -Christina -Christine -Christinia -Christof -Christofer -Christoper -Christoph -Christophe -Christopher -Christos -Christy -Chrystal -Chrystelle -Chrystian -Chu -Chuan -Chuck -Chuks -Chun -Chune -Chunfen -Chung -Chunguang -Chunhong -Chunhua -Chunlan -Chunlei -Chunli -Chunlian -Chunling -Chunmei -Chunming -Chunping -Chunsheng -Chunwei -Chunxia -Chunxiang -Chunxiao -Chunyan -Chunying -Cian -Ciara -Ciaran -Cibeles -Cibran -Cicely -Cicero -Cidalia -Cielo -Cielos -Ciera -Cierra -Cilene -Cilia -Cilinia -Cin -Cinda -Cinderella -Cindi -Cindia -Cindie -Cindy -Cinta -Cinthia -Cinthya -Cintia -Cinto -Cintya -Cinzia -Ciprian -Cipriana -Cipriano -Cira -Circe -Circuncision -Cirenia -Ciria -Ciriaca -Ciriaco -Cirila -Cirilo -Cirina -Cirino -Ciro -Cisne -Cisse -Cita -Citlalli -Clair -Claire -Clamores -Clara -Clare -Clarena -Clarence -Clarencia -Clarencio -Claret -Claretha -Claretta -Claribel -Clarice -Clarinda -Clarine -Clarines -Claris -Clarisa -Clarissa -Clarisse -Clarita -Claritza -Clarivel -Clariza -Clark -Claro -Classie -Claud -Claude -Claudelina -Claudemir -Claudete -Claudette -Claudi -Claudia -Claudiana -Claudiane -Claudiano -Claudie -Claudina -Claudine -Claudinei -Claudineia -Claudino -Claudio -Claudiu -Claus -Claustre -Claustro -Clavelina -Claver -Clay -Clayton -Clea -Cleber -Cleia -Cleide -Cleiton -Clelia -Clemence -Clemencia -Clemencio -Clemens -Clement -Clementa -Clemente -Clementina -Clementine -Clementino -Clemira -Clemmie -Clemorisa -Cleo -Cleofas -Cleofe -Cleonice -Cleopatra -Cleora -Cleotilde -Cleta -Cleto -Cletus -Cleusa -Cleuza -Cleveland -Cliff -Clifford -Clifton -Climaco -Climent -Climente -Clint -Clinton -Clio -Clive -Clodoalda -Clodoaldo -Clodomira -Clodomiro -Cloe -Clora -Clorinda -Clotilde -Clovis -Clyde -Coca -Cocepcion -Cocuta -Code -Codes -Codi -Codou -Codrut -Codruta -Cody -Cointa -Colas -Colby -Cole -Coleen -Coleman -Colene -Coletta -Colette -Colin -Collado -Colleen -Collell -Collen -Collene -Collette -Collin -Collins -Coloma -Coloman -Colombina -Colon -Colton -Columba -Columbano -Columbiana -Columbiano -Columbus -Coman -Comfort -Conceiçao -Concepcio -Concepcion -Conception -Concesa -Concesina -Conceso -Concetta -Concha -Conchi -Conchin -Conchita -Concordia -Confesor -Confesora -Cong -Conmemoracion -Connie -Connor -Conny -Conor -Conrad -Conrada -Conrado -Consagracion -Consejo -Consol -Consolacio -Consolacion -Consorcia -Constança -Constance -Constancia -Constancio -Constanta -Constante -Constanti -Constantin -Constantina -Constantino -Constanza -Constanze -Consuela -Consuelo -Contacto -Contemplacion -Contessa -Conversion -Conxa -Conxita -Cora -Coraima -Coral -Coralee -Corali -Coralia -Coralie -Coralina -Coralio -Corayma -Corazon -Cordelia -Cordell -Cordia -Cordie -Cordula -Core -Coreen -Corene -Coretta -Corey -Cori -Corie -Corin -Corina -Corine -Corinna -Corinne -Corliss -Cornel -Cornelia -Cornelio -Cornelis -Corneliu -Cornelius -Cornell -Coro -Coromoto -Corona -Coronacion -Coronada -Corono -Corpus -Corrado -Corrie -Corrin -Corrina -Corrine -Corrinne -Corsina -Corsino -Cortes -Cortez -Cortijo -Cortney -Cory -Cosima -Cosimo -Cosme -Cosmin -Cosmina -Costache -Costantin -Costantino -Costanza -Costel -Costela -Costica -Costin -Costina -Costinel -Costinela -Coulibaly -Coumba -Courage -Courtney -Covadonga -Coy -Cozmin -Craciun -Craig -Creacion -Crecencia -Crecencio -Cremencio -Crenguta -Creola -Crescencia -Crescenciana -Crescenciano -Crescencio -Crescente -Cresencia -Cresencio -Creu -Creuza -Crhistian -Crina -Criptana -Cris -Crisan -Crisanta -Crisanto -Crisantos -Criseida -Criselda -Crisogono -Crisologo -Crisostoma -Crisostomo -Crispin -Crispina -Crispiniano -Crispo -Crispula -Crispulo -Crissy -Crista -Cristabel -Cristache -Cristal -Cristalina -Cristea -Cristel -Cristela -Cristelle -Cristen -Cristeta -Cristhian -Cristhofer -Cristi -Cristia -Cristiam -Cristian -Cristiana -Cristiane -Cristiano -Cristie -Cristin -Cristina -Cristine -Cristinel -Cristinela -Cristino -Cristo -Cristoba -Cristobal -Cristobalina -Cristofer -Cristoffer -Cristofher -Cristofol -Cristofor -Cristophe -Cristopher -Cristy -Crsitina -Cruces -Crucita -Cruz -Crysta -Crystal -Crystian -Crystle -Csaba -Csilla -Cuadros -Cuauhtemoc -Cuc -Cueva -Cuie -Cuifen -Cuihong -Cuihua -Cuimei -Cuiping -Cuiying -Cuizhu -Culita -Cunegunda -Curro -Curt -Curtis -Custodia -Custodio -Cvetan -Cvetelina -Cyndi -Cyndy -Cynthia -Cyntia -Cyra -Cyril -Cyrille -Cyrstal -Cyrus -Cythia -Czeslaw -Czeslawa -D'Arc -D'Asis -D'Assis -Da -Daba -Dabi -Dabid -Dabo -Daby -Dace -Dacia -Dacian -Daciana -Daciano -Dacil -Dacio -Dadhichi -Dadi -Dado -Dafina -Dafinka -Dafne -Dagmar -Dagmara -Dagny -Dagoberto -Dah -Dahab -Dahane -Dahba -Dahbia -Dahiana -Dahlia -Dahman -Dahmane -Dahou -Daiana -Daiane -Daiara -Daida -Daila -Dailo -Dailos -Daily -Dailyn -Daina -Daine -Dainius -Daira -Dairen -Dairo -Dairon -Daisey -Daisy -Daiva -Dajana -Dajun -Dakota -Daksha -Dalal -Dalcy -Dale -Dalene -Dali -Dalia -Dalibor -Dalida -Dalila -Dalius -Daliza -Daljeet -Daljit -Dallas -Dalma -Dalmacia -Dalmacio -Dalmau -Dalmira -Dalmiro -Dalton -Dalva -Damari -Damaris -Damary -Damarys -Damasa -Damaso -Dame -Damia -Damian -Damiana -Damiano -Damien -Damion -Damir -Damon -Damyan -Dan -Dana -Danae -Danail -Danay -Dancho -Dandan -Dane -Danel -Danelle -Danelly -Danette -Danguole -Dani -Dania -Danial -Danica -Daniel -Daniela -Daniele -Daniell -Daniella -Danielle -Danielly -Daniil -Danijela -Danika -Danil -Danila -Danille -Danilo -Danish -Danita -Danitza -Danka -Dann -Danna -Dannette -Dannie -Dannielle -Danny -Dansk -Dante -Danut -Danuta -Danute -Dany -Danya -Danyel -Danyell -Danyelle -Danylo -Daoiz -Daoud -Daouda -Daouia -Daour -Daouya -Daphine -Daphne -Dara -Daray -Darby -Darc -Darcel -Darcey -Darci -Darcie -Darcy -Darejan -Darek -Darell -Daren -Dari -Daria -Darian -Dariana -Dariel -Darien -Darifa -Darin -Darina -Darinka -Dario -Darius -Dariusz -Dariya -Darja -Darko -Darla -Darleen -Darlena -Darlene -Darlin -Darline -Darling -Darnell -Daron -Darrel -Darrell -Darren -Darrick -Darrin -Darron -Darryl -Darshan -Darvin -Darwin -Dary -Darya -Daryl -Daryna -Dasha -Dasio -Dativa -Dativo -Daud -Dauda -Daura -Dave -Davi -David -Davida -Davide -Davina -Davinder -Davinia -Davis -Davit -Daviti -Davor -Davy -Davyd -Dawda -Dawei -Dawid -Dawit -Dawn -Dawna -Dawne -Dawood -Daya -Dayami -Dayan -Dayana -Dayanara -Dayane -Dayani -Dayanis -Dayanna -Dayara -Dayle -Daylin -Daylo -Daylos -Daymara -Daymi -Dayna -Dayong -Dayra -Dayron -Daysi -Deñe -Dea -Deadra -Dean -Deana -Deandra -Deandre -Deandrea -Deane -Deangelo -Deann -Deanna -Deanne -Deb -Debbi -Debbie -Debbra -Debby -Debera -Debi -Debla -Debora -Deborah -Debra -Debrah -Debroah -Decebal -Declan -Decorosa -Decoroso -Dede -Dedicacion -Dedra -Dee -Deeann -Deeanna -Deedee -Deedra -Deena -Deepa -Deepak -Deetta -Dei -Deiane -Deiby -Deicy -Deidamia -Deidra -Deidre -Deiene -Deimante -Deirdre -Deise -Deisi -Deisy -Deivi -Deivid -Deividas -Deivis -Deivy -Deja -Dejan -Del -Delaine -Delana -Delbert -Delcho -Delcie -Delcy -Delena -Delfi -Delfim -Delfin -Delfina -Delfino -Delia -Delicia -Delicias -Delila -Delilah -Delina -Delinda -Delio -Delioma -Delirio -Delisa -Dell -Della -Dellanira -Delma -Delmar -Delmer -Delmira -Delmiro -Delmy -Delois -Deloise -Delora -Deloras -Delores -Deloris -Delorse -Delpha -Delphia -Delphine -Delsie -Delta -Delyan -Demarcus -Demba -Dembele -Dembo -Demelsa -Demelza -Demetra -Demetri -Demetria -Demetrice -Demetrio -Demetrius -Demi -Demian -Demir -Demofilo -Dena -Denae -Denar -Deneen -Denes -Denese -Deni -Denia -Denice -Denilson -Denis -Denisa -Denise -Denisha -Denislav -Denislava -Deniss -Denisse -Denita -Denitsa -Deniz -Denka -Denna -Dennis -Dennise -Denny -Dennys -Denver -Denys -Denyse -Denzel -Deodato -Deogracia -Deogracias -Deolinda -Deolindo -Deon -Deonna -Dereck -Derek -Derick -Deriman -Derlis -Derly -Derrick -Desamparado -Desamparados -Desamparats -Deseada -Deseado -Deshawn -Desheng -Desideria -Desiderio -Desirae -Desire -Desiree -Desislav -Desislava -Desmond -Despina -Dessie -Dessire -Dessiree -Dessislava -Destiny -Detelin -Detelina -Detlef -Detra -Deu -Deus -Deva -Devabhuti -Devajuti -Devala -Devi -Devid -Devin -Devinder -Devon -Devona -Devora -Devorah -Dewayne -Dewey -Dewitt -Dexter -Deyan -Deyanira -Deysi -Deyvid -Dharma -Dharminder -Di -Dia -Diabel -Diaby -Diadie -Diadji -Diagne -Diago -Diakaridia -Diakite -Dialla -Diallo -Diamantina -Diamantino -Diamar -Diamela -Diamond -Dian -Diana -Diandra -Diane -Dianelys -Diango -Diann -Dianna -Dianne -Diao -Diara -Diarra -Diawara -Diawoye -Dick -Dickson -Dicra -Dictina -Dictinio -Dictino -Dida -Didac -Didi -Didier -Didimo -Didina -Diedra -Diedre -Diego -Dieguina -Dienaba -Dienabou -Dieneba -Dieng -Dierdre -Dieter -Dietmar -Dietrich -Dieudonne -Dieynaba -Digna -Digno -Dignora -Dijana -Diko -Dikra -Dilan -Dilara -Dilawar -Dilbag -Dilcia -Dilenia -Dilia -Dilip -Dillon -Dilma -Dilson -Dilyan -Dilyana -Dima -Dimas -Dimcho -Dimitar -Dimitra -Dimitre -Dimitri -Dimitrichka -Dimitrie -Dimitrina -Dimitrinka -Dimitrios -Dimitris -Dimitriy -Dimitrov -Dimitrova -Dimitru -Dimitry -Dimka -Dimo -Dimple -Din -Dina -Dinah -Dinara -Dine -Dinesh -Dinis -Dinka -Dinko -Dino -Dinora -Dinorah -Dinu -Diocelina -Diocleciano -Diodora -Diodoro -Diogenes -Diogo -Diolinda -Diomar -Diomedes -Dion -Dione -Dioni -Dionicia -Dionicio -Dionila -Dionis -Dionisia -Dionisie -Dionisio -Dionna -Dionne -Diop -Dior -Dios -Dioscorides -Dioscoro -Diosdada -Diosdado -Dioselina -Diosinda -Diosnel -Diouf -Dioulde -Diouma -Dipa -Dirce -Dirceu -Dirk -Discusion -Disney -Dita -Diti -Diva -Divina -Divine -Divino -Divya -Dixie -Diyan -Diyana -Djaffar -Djamal -Djamel -Djamila -Djelloul -Djenabou -Djeneba -Djenebou -Djibi -Djibril -Djiby -Djiguiba -Djilali -Djillali -Djily -Djime -Dmitri -Dmitrii -Dmitrij -Dmitrijs -Dmitriy -Dmitry -Dmytro -Doa -Doaa -Doaae -Doae -Dobre -Dobri -Dobrin -Dobrinka -Dobrita -Dobromir -Dochia -Dochka -Dodie -Doha -Doina -Doinita -Dolça -Dollie -Dolly -Dolnoserbski -Dolores -Doloris -Dolors -Doltza -Doly -Dombina -Domenec -Domenech -Domenic -Domenica -Domenico -Domicia -Domiciana -Domiciano -Domicio -Domina -Dominador -Dominga -Domingas -Domingo -Domingos -Dominic -Dominica -Dominick -Dominico -Dominik -Dominika -Dominique -Dominque -Dominykas -Domitila -Domitilo -Domnica -Domnina -Domnino -Domnita -Domonique -Don -Dona -Donaciana -Donaciano -Donaciones -Donald -Donat -Donata -Donatas -Donatella -Donatila -Donatilo -Donato -Doncho -Donelia -Donelio -Donella -Donetta -Donette -Dong -Dongdong -Dongfen -Donghai -Dongmei -Dongping -Dongsheng -Donia -Donika -Donina -Donino -Donis -Donita -Donka -Donn -Donna -Donnell -Donnetta -Donnette -Donnie -Donny -Donovan -Donte -Donvina -Donya -Dora -Doradia -Doralba -Doralia -Doralice -Doralicia -Doralina -Doraliza -Doramas -Dorathy -Dorca -Dorcas -Doreatha -Doreen -Dorel -Dorene -Dores -Doretha -Dorethea -Doretta -Dori -Doria -Dorian -Doriana -Dorica -Dorie -Dorila -Dorin -Dorina -Dorinda -Dorindo -Dorine -Dorinel -Dorinela -Doris -Dorit -Dorita -Dorka -Dorla -Dorleta -Doro -Dorota -Dorotea -Doroteo -Dorotha -Dorothea -Dorothee -Dorothy -Dorris -Dorsey -Dorte -Dortha -Dorthe -Dorthea -Dorthey -Dorthy -Doru -Dorut -Dory -Dorys -Dosinda -Dosindo -Dositeo -Dot -Dottie -Dotty -Doua -Douaa -Douaae -Douae -Doudou -Doug -Douga -Douglas -Douglass -Douha -Doumbia -Dounia -Dounya -Dovie -Dovile -Dovydas -Doyle -Dragan -Dragana -Dragica -Drago -Dragomir -Dragos -Draguta -Dramane -Drame -Draupadi -Drazen -Dreama -Drema -Drew -Drifa -Drina -Dris -Driss -Drissa -Drissia -Dritan -Dritarastra -Drucilla -Drusila -Drusilla -Duaa -Duane -Duarte -Duberney -Dudley -Duguneh -Duha -Dulce -Dulcelina -Dulcenombre -Dulcerina -Dulcie -Dulcina -Dulcinea -Dulia -Dulzura -Dumitra -Dumitrache -Dumitrel -Dumitrita -Dumitru -Duna -Duncan -Dune -Dung -Dunia -Dunixe -Dunja -Dunya -Durvasa -Duryodhana -Dusan -Dusti -Dustin -Dusty -Duvan -Duverney -Dwain -Dwana -Dwarka -Dwayne -Dwight -Dyan -Dylan -Dzianis -Dzmitry -Eñaut -Eamon -Earl -Earle -Earlean -Earleen -Earlene -Earlie -Earline -Earnest -Earnestine -Eartha -Easter -Eba -Ebenezer -Eber -Eberhard -Eboni -Ebonie -Ebony -Ebou -Ebrahim -Ebrahima -Ebrima -Ebru -Ebtisam -Ecaterina -Echedey -Echo -Eckhard -Ed -Eda -Edda -Eddie -Eddin -Eddine -Eddy -Edel -Edelfina -Edelgard -Edelia -Edelina -Edelio -Edelma -Edelmira -Edelmiro -Edeltraud -Edeltraut -Edelvina -Edelweis -Edelweiss -Eden -Edenia -Eder -Ederlinda -Ederne -Ederson -Edesia -Edesio -Edey -Edgar -Edgaras -Edgard -Edgardo -Edgars -Edi -Edicta -Edie -Edier -Edik -Edil -Edilberta -Edilberto -Edilene -Edileuza -Edilia -Edilio -Edilma -Edilson -Edin -Edina -Edinalva -Edineia -Edinson -Edisa -Edison -Edisson -Edit -Edita -Edite -Edith -Editha -Edivaldo -Edivia -Edmar -Edmilson -Edmon -Edmond -Edmund -Edmunda -Edmundas -Edmundo -Edna -Edoardo -Edorta -Edouard -Edra -Edris -Edrisa -Edson -Edu -Eduar -Eduard -Eduarda -Eduardas -Eduardo -Eduart -Eduin -Eduina -Edur -Edurne -Eduviges -Eduvigis -Eduvijis -Eduvina -Edvaldo -Edvard -Edvardas -Edvin -Edvinas -Edwar -Edward -Edwardo -Edwige -Edwin -Edwina -Edy -Edyta -Edyth -Edythe -Eero -Efe -Effie -Efidencia -Efigenia -Efigenio -Efisio -Efosa -Efraim -Efrain -Efran -Efrem -Efren -Eghosa -Egiarte -Egidia -Egidijus -Egidio -Egil -Egle -Egoi -Egoitz -Egon -Egor -Eguskiñe -Eguzkiñe -Eguzki -Eguzkine -Ehab -Ehari -Ehedei -Ehedey -Ehsan -Ehtel -Ehtisham -Eidan -Eiden -Eider -Eihar -Eiharne -Eiko -Eila -Eileen -Eilene -Eiman -Eimantas -Eimi -Eimy -Einar -Eira -Eire -Eirik -Eitan -Eithan -Eizan -Ejaz -Eka -Ekai -Ekain -Ekaitz -Ekaterina -Ekaterine -Ekhiñe -Ekhi -Ekhine -Ekhiotz -Ekiñe -Eki -Ekram -El -Ela -Eladi -Eladia -Eladina -Eladino -Eladio -Elaia -Elaid -Elaina -Elaine -Elan -Elana -Elane -Elanor -Elarbi -Elayne -Elba -Elbachir -Elbert -Elbio -Elcira -Elcy -Elda -Eldar -Elden -Elder -Eldon -Eldora -Eldridge -Eldy -Elea -Eleanor -Eleanora -Eleanore -Elease -Eleazar -Electa -Electo -Electra -Eleder -Elen -Elena -Elene -Eleni -Elenice -Elenita -Elenka -Eleno -Elenor -Elenora -Elenore -Eleodora -Eleodoro -Eleonor -Eleonora -Eleonore -Eleuteria -Eleuterio -Elfi -Elfidio -Elfreda -Elfrieda -Elfriede -Elga -Elhadji -Elham -Elhassan -Eli -Elia -Eliam -Elian -Eliana -Eliane -Elianne -Eliany -Elias -Eliazar -Eliberta -Eliberto -Elica -Eliceo -Elicerio -Elicia -Elicinia -Elicio -Elida -Elide -Elidia -Elidio -Eliduvina -Elie -Eliecer -Eliel -Eliene -Elier -Elies -Elieser -Eliete -Eliezer -Elif -Elifio -Eligia -Eligijus -Eligio -Elihu -Elijah -Elimane -Elin -Elina -Eline -Elinor -Elinore -Elio -Eliodora -Eliodoro -Elionor -Elios -Eliot -Eliott -Eliria -Elis -Elisa -Elisabeht -Elisabel -Elisabet -Elisabeta -Elisabete -Elisabeth -Elisabetta -Elisabhet -Elisandra -Elisangela -Elisardo -Elisaveta -Elise -Elisea -Elisei -Elisenda -Eliseo -Elisete -Eliseu -Elisha -Elisia -Elisio -Eliska -Eliso -Elissa -Elita -Elitsa -Eliu -Eliud -Elixabet -Elixabete -Elixane -Eliz -Eliza -Elizabe -Elizabeht -Elizabel -Elizabet -Elizabeta -Elizabete -Elizabeth -Elizabhet -Elizangela -Elizaveta -Elizbeth -Elizebeth -Elizete -Elka -Elke -Elkin -Ella -Ellamae -Ellan -Ellande -Ellen -Ellena -Elli -Ellie -Elliot -Elliott -Ellis -Ellsworth -Elly -Ellyn -Elm -Elma -Elmahdi -Elmar -Elmehdi -Elmer -Elmira -Elmo -Elna -Elnora -Elodia -Elodie -Elody -Eloi -Eloina -Eloino -Elois -Eloisa -Eloise -Elora -Elorri -Elouise -Eloy -Eloysa -Elpidia -Elpidio -Elroy -Els -Elsa -Elsbeth -Else -Elsi -Elsie -Elsira -Elsita -Elson -Elsy -Elton -Eludina -Eluney -Elur -Eluska -Elva -Elver -Elvera -Elvia -Elvie -Elvin -Elvina -Elvio -Elvira -Elviro -Elvis -Elvita -Elwanda -Elwood -Ely -Elyas -Elyass -Elyse -Elza -Elzbieta -Ema -Emad -Eman -Emanoil -Emanuel -Emanuela -Emanuele -Emanuil -Embarec -Embarek -Embarka -Emeka -Emel -Emelda -Emelia -Emelina -Emelinda -Emeline -Emelita -Emely -Emelyn -Emerald -Emerencia -Emerenciana -Emerenciano -Emeric -Emerico -Emerida -Emerinda -Emerio -Emerita -Emerito -Emerson -Emery -Emese -Emeteria -Emeterio -Emi -Emidio -Emigdia -Emigdio -Emiko -Emil -Emila -Emilce -Emile -Emilee -Emilene -Emili -Emilia -Emilian -Emiliana -Emiliano -Emilie -Emilienne -Emilija -Emilio -Emiliya -Emiliyan -Emilly -Emilo -Emilse -Emilsen -Emily -Emin -Emina -Emine -Emir -Emma -Emmaline -Emmanouil -Emmanuel -Emmanuela -Emmanuelle -Emmett -Emmie -Emmitt -Emmy -Emogene -Emoke -Emory -Empar -Emperatriz -Emran -Emre -Emy -En -Ena -Enache -Enai -Enaitz -Enar -Enara -Enaut -Encarna -Encarnacio -Encarnacion -Encarni -Encarnita -Encho -Encina -Encinar -Enda -Endika -Endre -Endurance -Ene -Enea -Eneas -Enebie -Enedina -Enedino -Eneida -Eneka -Eneko -Enekoitz -Enelia -Enelida -Enemesia -Enemesio -Eneritz -Eneriz -Enetz -Engelbert -Engracia -Engracio -Enguia -Eni -Enia -Enid -Eniko -Enilda -Enio -Enith -Enma -Enmanuel -Enmanuela -Enna -Ennaji -Ennio -Enoc -Enoch -Enoelia -Enol -Enola -Enora -Enos -Enric -Enrica -Enrico -Enrike -Enrique -Enriqueta -Enrrique -Enver -Enya -Enza -Enzo -Epifania -Epifanio -Epigmenio -Era -Eradio -Eraldo -Eralia -Erasma -Erasmo -Eray -Ercan -Ercilia -Erea -Erena -Erenia -Eresvita -Erhan -Erhard -Erhimo -Erian -Eriberto -Eric -Erica -Erich -Erick -Ericka -Erico -Ericson -Eridania -Erik -Erika -Erikas -Eriko -Erin -Erina -Erinn -Eritz -Eriz -Erkan -Erkuden -Erlaitz -Erlan -Erlantz -Erlene -Erlinda -Erline -Erling -Erma -Ermanno -Ermelina -Ermelinda -Ermengol -Ermerinda -Ermes -Ermesinda -Ermila -Ermina -Erminda -Erminia -Erminio -Ermita -Ermitas -Erna -Ernest -Ernesta -Ernestas -Ernestina -Ernestine -Ernestino -Ernesto -Ernie -Erno -Ernst -Ero -Erol -Erola -Erondina -Eros -Erotida -Erradi -Erramun -Errol -Ersilia -Ersin -Erudina -Erundina -Erundino -Ervigio -Ervin -Erwan -Erwin -Eryk -Eryka -Eryn -Erzsebet -Es -Esau -Esaul -Escarlata -Escelita -Esclavitud -Escolastica -Escolastico -Esdras -Esha -Eskarne -Esma -Esmael -Esmail -Esmelda -Esmeralda -Esmeraldo -Esmerinda -Esneda -Esneider -Esohe -Esosa -España -Espartaco -Espectacion -Espen -Esperança -Esperanto -Esperanza -Espino -Espiridion -Espiritu -Espiritusanto -Essa -Essaadia -Essaddik -Essadia -Essadik -Essaid -Essam -Essie -Esta -Estafania -Estanis -Estanisla -Estanislaa -Estanislada -Estanislao -Estanislau -Esteban -Estebana -Estebania -Estebe -Estefan -Estefana -Estefani -Estefania -Estefanny -Estefano -Estefany -Estel -Estela -Estelia -Estelita -Estell -Estella -Estelle -Estelvina -Estephania -Estephany -Ester -Estera -Estervina -Estevan -Esteve -Esteven -Esthefania -Esthefany -Esthela -Esther -Estibalitz -Estibaliz -Estiben -Estilita -Estitxu -Estivaliz -Estiven -Estrela -Estrella -Estrellita -Estuardo -Estudita -Eszter -Etel -Etelfrido -Etelvina -Etelvino -Eteri -Eterio -Etha -Ethan -Ethel -Ethelbaldo -Ethelene -Ethelyn -Ethyl -Etienne -Etna -Etor -Etsuko -Etta -Ettie -Ettore -Etxahun -Eucaris -Euclides -Eudald -Eudaldo -Eudardo -Eudelia -Eudes -Eudochia -Eudocia -Eudosia -Eudosio -Eudoxia -Eudoxio -Eufemia -Eufemiano -Eufemio -Eufracia -Eufrasia -Eufrasio -Eufronia -Eufronio -Eufrosina -Eugen -Eugena -Eugene -Eugeni -Eugenia -Eugenie -Eugenijus -Eugenio -Eugeniu -Eugeniusz -Eugeniy -Eugeniya -Eugeny -Euken -Eukene -Eukeni -Eula -Eulah -Eulalia -Eulalio -Eulises -Eulogi -Eulogia -Eulogio -Eumelia -Eumenio -Eun -Euna -Eunate -Eunice -Euquerio -Eura -Eurico -Euridice -Eusebi -Eusebia -Eusebio -Eusebiu -Eusiquio -Eustaquia -Eustaquio -Eustasia -Eustasio -Eusterio -Eustolia -Eustoquia -Eustoquio -Eustorgio -Eutilia -Eutilio -Eutimia -Eutimio -Eutiquia -Eutiquiano -Eutiquio -Eutropia -Eutropio -Eva -Evaldas -Evalyn -Evan -Evandro -Evangelia -Evangelica -Evangelina -Evangeline -Evangelino -Evangelista -Evangelos -Evans -Evarist -Evarista -Evaristo -Evdochia -Evdokia -Eve -Evedasto -Eveli -Evelia -Evelin -Evelina -Eveline -Evelio -Evelyn -Evelyne -Evelynn -Evencia -Evencio -Ever -Everaldo -Everardo -Everett -Everette -Everilda -Evert -Everth -Everton -Evette -Evgeni -Evgenia -Evgeniy -Evgeniya -Evgeny -Evgueni -Evguenia -Evi -Evia -Evie -Evilasia -Evilasio -Evilia -Evilio -Evita -Evodia -Evolet -Evon -Evonne -Evora -Ewa -Ewald -Ewan -Ewelina -Exaltacion -Excelsa -Excelsina -Exequiel -Exie -Exiquia -Exiquio -Expectacion -Expedita -Expedito -Expiracion -Exuperancia -Exuperancio -Exuperio -Eyal -Eydan -Eylo -Eyre -Ez -Ezahra -Ezekiel -Ezequiel -Ezequiela -Ezio -Ezra -Eztizen -Ezzahra -Ezzahraa -Ezzahrae -Ezzohra -Ezzouhra -F -Faber -Fabia -Fabian -Fabiana -Fabiane -Fabiano -Fabien -Fabienne -Fabio -Fabiola -Fabrice -Fabricia -Fabriciana -Fabriciano -Fabricio -Fabrizio -Fabrizzio -Facunda -Facundo -Fadel -Fadela -Fadhila -Fadi -Fadia -Fadiala -Fadil -Fadila -Fadma -Fadoua -Fadrique -Fadua -Fadwa -Fady -Fae -Fahad -Fahd -Faheem -Fahim -Fahima -Faical -Faik -Faina -Fairouz -Fairy -Faisal -Faissal -Faith -Faiz -Faiza -Faizan -Fakhar -Fala -Falak -Falgas -Falilou -Falk -Fall -Fallon -Fallou -Falou -Faly -Fama -Famakan -Famara -Famory -Fan -Fana -Fandila -Fane -Fanel -Fang -Fangfang -Fani -Fania -Fanica -Fanida -Fanka -Fanni -Fannie -Fanny -Fanor -Fanta -Fanuta -Fany -Faouzi -Faouzia -Fara -Farah -Faraji -Faraz -Fares -Farha -Farhad -Farhan -Farhana -Farhat -Farid -Farida -Faride -Faris -Fariza -Farners -Farnes -Farooq -Farouk -Farrah -Farrukh -Faruk -Farwa -Farzana -Fatah -Fateh -Fatema -Fatemeh -Faten -Fath -Fathallah -Fathi -Fathia -Fati -Fatih -Fatiha -Fatim -Fatima -Fatimah -Fatimata -Fatimatou -Fatimatu -Fatimazahra -Fatime -Fatimetou -Fatimetu -Fatin -Fatine -Fatma -Fatme -Fatna -Fatoma -Fatou -Fatoum -Fatouma -Fatoumata -Fatoumatta -Fattah -Fattouch -Fattoum -Fattouma -Fatu -Fatumata -Faty -Fausi -Fausia -Faust -Fausta -Fausti -Faustina -Faustino -Fausto -Fauzi -Fauzia -Favio -Faviola -Favour -Fawaz -Fawn -Fawzi -Fawzia -Fayçal -Fay -Faycal -Faye -Fayez -Fayna -Fayrouz -Faysal -Fayssal -Fayyaz -Fayza -Fazal -Fco -Fdila -Fe -Febe -Federic -Federica -Federico -Fedir -Fedor -Fedora -Fedoua -Fedra -Fei -Feifei -Feiyan -Fekri -Felecia -Feliberto -Felica -Felice -Felicia -Felician -Feliciana -Feliciano -Felicidad -Felicinda -Felicio -Felicisima -Felicisimo -Felicita -Felicitacion -Felicitas -Felicitat -Felicito -Feliks -Felina -Felio -Felip -Felipa -Felipe -Felisa -Felisardo -Felisbela -Felisberto -Felisha -Felisinda -Felisindo -Feliu -Felix -Feliz -Feliza -Fella -Felton -Femke -Fen -Fenfen -Feng -Fengmei -Fengqin -Fengying -Fengyun -Fengzhu -Fenna -Feodor -Feras -Ferdaous -Ferdaouss -Ferdaus -Ferdaws -Ferdinand -Ferdinando -Ferenc -Ferencz -Ferhat -Ferial -Feriel -Fermi -Fermin -Fermina -Fern -Fernan -Fernand -Fernanda -Fernande -Fernandina -Fernando -Ferne -Ferney -Ferran -Ferreol -Ferrer -Ferriol -Ferruccio -Festus -Fethi -Fettah -Fettoum -Fettouma -Fiama -Fiamma -Fiaz -Fida -Fidanka -Fidel -Fidela -Fidelia -Fidelina -Fidelino -Fidelio -Fidelis -Fidencia -Fidenciano -Fidencio -Fidentina -Fidentino -Fikret -Fikri -Fikria -Filadelfo -Filemon -Fileto -Filiberta -Filiberto -Filimon -Filip -Filipa -Filipe -Filipp -Filippo -Filo -Filofteia -Filoftia -Filomena -Filomeno -Filonila -Fily -Fina -Fineas -Finn -Finnian -Fiodor -Fiona -Fior -Fiordaliza -Fiorela -Fiorella -Firas -Firdaous -Firdaouss -Firdaus -Firdaws -Firmino -Firmo -Firuta -Fiz -Fiza -Flaminio -Flavia -Flavian -Flaviana -Flaviano -Flavio -Flaviu -Flavius -Flemming -Flerida -Fleta -Fletcher -Fleur -Flo -Floare -Floarea -Flor -Flora -Floralba -Florance -Florangel -Florbela -Flordeliza -Flore -Florea -Floreal -Florean -Floreano -Florence -Florenci -Florencia -Florencio -Florene -Florent -Florenta -Florenti -Florentin -Florentina -Florentino -Flores -Floretta -Flori -Floria -Florian -Floriana -Floriano -Florica -Floricel -Floricica -Florida -Florin -Florina -Florinda -Florindo -Florine -Florinel -Florinela -Floripe -Floripes -Floris -Florisa -Florita -Floro -Florrie -Flors -Flossie -Floy -Floyd -Foad -Foday -Fode -Fodie -Fodil -Fofana -Foix -Fonda -Fontcalda -Forest -Formerio -Forrest -Fortia -Fortuna -Fortunata -Fortunato -Foster -Fouad -Founeke -Fousseni -Fousseny -Fousseyni -Fouzi -Fouzia -Fouziya -Fozia -Français -François -Françoise -Fran -Franc -Franca -France -Francelina -Francene -Frances -Francesc -Francesca -Francesco -Franchesca -Francheska -Francho -Francia -Francie -Franciele -Francina -Francine -Francis -Francisc -Francisca -Francisco -Franciscus -Franciska -Franciszek -Franck -Franco -Francoise -Francy -Frank -Frankie -Franklin -Franklyn -Frans -Fransisca -Frantisek -Franz -Franziska -Fraternidad -Frauke -Fred -Freda -Fredda -Freddie -Freddy -Frederic -Frederica -Frederick -Fredericka -Frederico -Frederik -Frederique -Fredesvinda -Fredesvindo -Fredeswinda -Fredi -Fredia -Fredric -Fredrick -Fredricka -Fredrik -Fredy -Freeda -Freeman -Freida -Freya -Frida -Friday -Frieda -Friederike -Friedhelm -Friedrich -Frits -Fritz -Frode -Froilan -Froilana -Fructuosa -Fructuoso -Frumencio -Frusina -Frutos -Ftimou -Fu -Fuad -Fuencisla -Fuensanta -Fuente -Fuentes -Fujun -Fulgenci -Fulgencia -Fulgencio -Fulvia -Fulvio -Fumiko -Fundador -Fundadora -Fundamento -Fuqiang -Furqan -Gabi -Gabija -Gabina -Gabino -Gabirel -Gabor -Gabriel -Gabriela -Gabriele -Gabriella -Gabrielle -Gabrielly -Gaby -Gadea -Gadiel -Gador -Gados -Gael -Gaelle -Gaetan -Gaetana -Gaetano -Gagandeep -Gagik -Gaia -Gaieta -Gail -Gaiska -Gaizka -Gaizkane -Gal -Gala -Galadriel -Galan -Galatea -Galaye -Galder -Galdric -Gale -Galen -Galia -Galilea -Galileo -Galin -Galina -Galla -Galo -Galya -Galyna -Gamal -Gamaliel -Gancho -Gandharva -Ganeko -Ganesh -Ganesha -Gang -Ganix -Ganka -Ganna -Gaofeng -Gaoussou -Gara -Garai -Garazi -Garbiñe -Garcia -Garcilaso -Gardenia -Garegin -Gareth -Garfield -Gari -Garik -Garikoitz -Garland -Garnet -Garnett -Garnik -Garoa -Garoafa -Garoe -Garofita -Garret -Garrett -Garry -Garth -Gartxot -Gartzen -Garuda -Gary -Gaspar -Gaspara -Gaspare -Gaston -Gaudelia -Gaudencia -Gaudencio -Gaudiosa -Gaudioso -Gaumet -Gaurav -Gautama -Gavin -Gavina -Gavino -Gavril -Gavrila -Gay -Gayane -Gaye -Gayla -Gayle -Gaylene -Gaylord -Gaynell -Gaynelle -Gaynor -Gazmira -Ge -Gea -Geane -Geanina -Gearldine -Gedeon -Gediminas -Geert -Geertje -Geertruida -Geeta -Gegham -Geidy -Geir -Geisa -Gela -Gelasio -Gelo -Gelu -Gema -Geminiano -Gemma -Gena -Genadi -Genadio -Genar -Genara -Genaro -Gencho -Gene -Gener -Generosa -Generoso -Genesis -Geneva -Genevie -Genevieve -Genevive -Geni -Genia -Genica -Genie -Genis -Genma -Genna -Gennadiy -Gennady -Gennaro -Gennie -Genny -Geno -Genova -Genovaite -Genoveba -Genoveva -Genowefa -Gentil -Gentza -Gentzane -Geny -Geoffrey -Georg -Georgann -George -Georgeann -Georgeanna -Georgel -Georgene -Georges -Georgeta -Georgetta -Georgette -Georghe -Georgi -Georgia -Georgian -Georgiana -Georgiann -Georgianna -Georgianne -Georgica -Georgie -Georgiev -Georgieva -Georgina -Georgine -Georgios -Georgiy -Georgy -Geovana -Geovane -Geovani -Geovanna -Geovanni -Geovanny -Gerad -Gerai -Gerald -Geralda -Geraldin -Geraldina -Geraldine -Geraldo -Geralyn -Gerard -Gerarda -Gerardina -Gerardo -Gerardus -Gerasimo -Geraxane -Geray -Gerd -Gerda -Geremias -Gergana -Gergely -Gergina -Gerhard -Geri -Gerino -Gerlinde -Germa -Germain -Germaine -German -Germana -Germania -Germanico -Germano -Germelina -Germina -Germinal -Germiniano -Gernot -Gero -Geroni -Geronima -Geronimo -Gerri -Gerrit -Gerry -Gersain -Gersom -Gerson -Gert -Gertha -Gertie -Gertraud -Gertrud -Gertrude -Gertrudes -Gertrudi -Gertrudis -Gertude -Gervacio -Gervasia -Gervasio -Gessami -Gessica -Geta -Getulio -Getuta -Gevorg -Gexan -Geza -Gezabel -Ghada -Ghaffar -Ghailan -Ghali -Ghalia -Ghania -Gharib -Ghariba -Ghassan -Ghazala -Ghazanfar -Ghazi -Ghazouani -Ghenadie -Gheorge -Gheorghe -Gheorghina -Gheorghita -Gheorgue -Gherasim -Gherghina -Gheroghe -Ghiorghe -Ghiorghi -Ghiorghita -Ghislaine -Ghita -Ghizela -Ghizlan -Ghizlane -Ghlana -Ghofran -Ghofrane -Gholamreza -Ghulam -Ghzala -Gia -Giacomo -Giada -Giampaolo -Giampiero -Gian -Giancarlo -Giancarlos -Gianella -Gianfranco -Giani -Gianina -Gianluca -Gianluigi -Gianmarco -Gianna -Gianni -Giannina -Gianpaolo -Gianpiero -Gibet -Gibril -Gica -Gicu -Gicuta -Gideon -Gidget -Giedre -Giedrius -Gift -Gifty -Giga -Gigel -Gigi -Gigliola -Gihan -Gil -Gila -Gilbert -Gilberta -Gilberte -Gilberto -Gilda -Gildardo -Gildo -Gilen -Gillen -Gillermo -Gilles -Gillian -Gilma -Gilmar -Gilmara -Gilmer -Gilson -Gimena -Gina -Ginebra -Ginel -Gines -Ginesa -Ginesta -Ginette -Ginevra -Ginger -Ginka -Ginna -Ginny -Gino -Gintaras -Gintare -Gintautas -Gioacchino -Gioconda -Gioia -Giordano -Giorgi -Giorgia -Giorgian -Giorgiana -Giorgica -Giorgina -Giorgio -Giovana -Giovani -Giovanna -Giovanni -Giovanny -Giovany -Giraldo -Girolamo -Gisbert -Gisel -Gisela -Giselda -Gisele -Gisell -Gisella -Giselle -Gislaine -Gislene -Gissel -Gissela -Gisselle -Gita -Gitana -Gitta -Gitte -Giulia -Giuliana -Giuliano -Giulietta -Giulio -Giuseppa -Giuseppe -Giuseppina -Givi -Gixane -Gizane -Gizela -Gizella -Gladis -Gladiz -Glady -Gladys -Glafira -Glaucia -Glayds -Gleb -Gleice -Glen -Glenda -Glendora -Glenis -Glenn -Glenna -Glennie -Glennis -Glenny -Glenys -Gleva -Gliceria -Glicerio -Gligor -Glinda -Gloria -Glorificacion -Glory -Glyn -Glynda -Glynis -Gnima -Goar -Gobrias -Gocha -Godelieve -Godfrey -Godofredo -Godspower -Godstime -Godwin -Gogu -Gohar -Goiatz -Goio -Goiuri -Goiuria -Goizalde -Goizane -Goizargi -Goizeder -Golam -Golda -Golden -Goldie -Goliat -Gonçal -Gonçalo -Gontzal -Gonzaga -Gonzala -Gonzalina -Gonzalo -Goodluck -Gopal -Gopala -Gopi -Gor -Gora -Goran -Gorane -Gordana -Gordiano -Gordon -Gorete -Goreti -Goretti -Goretty -Gorgonia -Gorgonio -Gorgui -Gori -Gorka -Gosho -Gospodin -Gottfried -Gotzon -Gotzone -Goundo -Govinda -Goya -Goyo -Graça -Graças -Grabiel -Grabiela -Graca -Gracas -Grace -Gracia -Gracian -Graciana -Graciano -Gracias -Gracie -Graciela -Graciella -Graciliana -Graciliano -Gracinda -Graciosa -Grady -Graeme -Graham -Graig -Gran -Granada -Grant -Granville -Gratiela -Gratiniano -Grau -Grayce -Grazia -Graziano -Graziela -Graziella -Grazina -Grazyna -Grecia -Greg -Gregg -Gregoire -Gregor -Gregori -Gregoria -Gregorio -Gregory -Greisy -Greta -Gretchen -Grete -Gretel -Grethe -Grethel -Gretta -Grettel -Gricel -Gricelda -Grietje -Grigor -Grigoras -Grigore -Grigori -Grigory -Grimanesa -Grisel -Grisela -Griselda -Grisha -Grit -Gro -Grober -Grover -Gruia -Grygoriy -Grzegorz -Guacimara -Guadalupe -Guaditoca -Guaire -Gualberto -Gualterio -Guang -Guanghua -Guanghui -Guangming -Guangrong -Guanjun -Guanmin -Guanping -Guarda -Guasimara -Guaxara -Guayarmina -Guayasen -Guayre -Gudelia -Gudrun -Gudula -Guendalina -Guenter -Guenther -Gueorgui -Guerau -Guery -Gueye -Guglielmo -Gui -Guia -Guiayara -Guida -Guido -Guiem -Guifang -Guifen -Guifre -Guihua -Guilermo -Guilhem -Guilherme -Guillaume -Guillem -Guillen -Guillerma -Guillerme -Guillermin -Guillermina -Guillermo -Guim -Guiomar -Guisela -Guiseppe -Guiu -Guiying -Guizlane -Gul -Gulfam -Gullermo -Gulnara -Gulnaz -Gulshan -Gulzar -Gumaro -Gumercinda -Gumercindo -Gumersinda -Gumersindo -Gundemaro -Gundula -Gunnar -Gunter -Gunther -Guo -Guobin -Guodong -Guofen -Guofeng -Guofu -Guoguang -Guohong -Guohua -Guohui -Guojun -Guoliang -Guomei -Guomin -Guoping -Guoqiang -Guoqing -Guoquan -Guorong -Guosheng -Guoxin -Guoxing -Guoying -Guoyong -Guozhong -Guram -Gurdeep -Gurdev -Gurdip -Gurgen -Gurinder -Gurjeet -Gurjinder -Gurjit -Gurleen -Gurmail -Gurmeet -Gurmit -Gurmukh -Gurnam -Gurpal -Gurpreet -Gurutz -Gurutze -Guruzne -Gurvinder -Gurwinder -Gus -Gussie -Gustau -Gustav -Gustavo -Gutier -Guy -Guzel -Guzman -Gwen -Gwenaelle -Gwenda -Gwendoline -Gwendolyn -Gwendolyne -Gwenn -Gwyn -Gwyneth -Gyongyi -Gyongyver -Gyorgy -Gytis -Gyula -Gyunay -Ha -Habib -Habiba -Habibou -Habibur -Haby -Hacen -Hacene -Hachem -Hachemi -Hachim -Hachmi -Hachmia -Hacomar -Hada -Hadda -Haddou -Haddoum -Hadduch -Hademou -Hades -Hadhoum -Hadi -Hadia -Hadil -Hadilla -Hadiya -Hadj -Hadjer -Hadji -Hadjira -Hadrian -Hady -Hae -Hafed -Hafeez -Hafid -Hafida -Hafiz -Hafsa -Hafssa -Hagar -Hagen -Hagi -Hagie -Hai -Haiat -Haibin -Haibo -Haidar -Haide -Haidee -Haider -Haidong -Haie -Haifen -Haifeng -Haiguang -Haihong -Haihua -Haijuan -Haijun -Hailey -Haili -Haimar -Haimei -Haimin -Haiou -Haiping -Haiqin -Hairong -Haitam -Haitao -Haitham -Haitz -Haiwei -Haixia -Haixiao -Haiyan -Haiying -Haiyong -Haiyun -Haize -Haizea -Haizeder -Haizene -Haizhen -Haizhu -Haj -Haja -Hajar -Hajer -Haji -Hajiba -Hajie -Hajira -Hajjaj -Hajnal -Hajnalka -Hajra -Hakan -Hakim -Hakima -Hakkoum -Hakob -Hal -Hala -Haleema -Haley -Halid -Halil -Halim -Halima -Halimatou -Halina -Hallar -Halley -Hallie -Hallouma -Halyna -Hama -Hamad -Hamada -Hamadi -Hamadou -Hamady -Hamama -Hamara -Hamdi -Hamed -Hamet -Hamid -Hamida -Hamido -Hamidou -Hamilton -Hamlet -Hamma -Hammad -Hammadi -Hammed -Hammou -Hamna -Hamou -Hamoudi -Hamsa -Hamudi -Hamza -Han -Hana -Hanaa -Hanadi -Hanae -Hanafi -Hanah -Hanan -Hanana -Hanane -Hang -Hanh -Hani -Hania -Hanibal -Hanif -Hanifa -Hanin -Hank -Hanna -Hannah -Hannan -Hannane -Hanne -Hannelore -Hannes -Hannibal -Hans -Hansel -Hany -Hanza -Hao -Haojie -Haoran -Haouari -Haoxiang -Haoxuan -Haoyu -Happy -Hapuc -Haralambie -Harald -Haran -Harbhajan -Hardeep -Hardip -Haresh -Hari -Haridiam -Haridian -Harinder -Haris -Harish -Haritz -Haritza -Harjeet -Harjinder -Harjit -Harkaitz -Harlan -Harland -Harley -Harm -Harmanpreet -Harminder -Harmony -Harnek -Haroa -Harol -Harold -Haroldo -Haron -Haroon -Haroun -Harouna -Harpal -Harpreet -Harri -Harriet -Harriett -Harriette -Harris -Harrison -Harry -Harsh -Hartmut -Harumi -Harun -Haruna -Harutyun -Harvey -Harvinder -Harwinder -Hasan -Hasana -Hasbia -Haseeb -Hashim -Hasier -Hasina -Hasmik -Hasna -Hasnaa -Hasnae -Hasnain -Hasni -Hasnia -Hassam -Hassan -Hassana -Hassane -Hassania -Hassen -Hassiba -Hassie -Hassina -Hassna -Hassnaa -Hassnae -Hatem -Hatim -Hatou -Hatoumata -Hattie -Hawa -Haya -Hayam -Hayar -Hayat -Hayate -Hayde -Haydee -Hayden -Hayet -Hayk -Haykanush -Hayley -Haytam -Haytham -Haywood -Hazael -Hazel -Hazem -He -Heath -Heather -Heba -Hebe -Heber -Hebert -Hecham -Hector -Hedda -Hedi -Hedwig -Hedy -Hee -Hegoa -Hegoi -Heide -Heidemarie -Heidi -Heidrun -Heidy -Heihachi -Heike -Heikki -Heiko -Heiner -Heinrich -Heinz -Heitor -Helaine -Helder -Helen -Helena -Helenca -Helene -Helenio -Heleodora -Helga -Helge -Heli -Helia -Heliana -Heliberto -Helida -Helio -Heliodora -Heliodoro -Helios -Hella -Helle -Hellen -Helma -Helmer -Helmut -Helmuth -Heloisa -Heloise -Helvia -Henar -Hend -Henda -Hendrik -Hendrika -Hendrikje -Hendrikus -Henedina -Heng -Henk -Hennadiy -Henning -Henny -Henoc -Henok -Henri -Henrieta -Henrietta -Henriette -Henrik -Henrikas -Henrique -Henrry -Henry -Henryk -Henryka -Heorhiy -Heping -Hera -Heraclia -Heraclides -Heraclio -Heradio -Herb -Herbert -Hercilia -Herculano -Hercules -Herculina -Heredia -Herena -Herenia -Heribert -Heriberta -Heriberto -Herica -Herlan -Herlinda -Herma -Herman -Hermandina -Hermann -Hermela -Hermelando -Hermelina -Hermelinda -Hermelindo -Hermelo -Hermencina -Hermenegil -Hermenegilda -Hermenegildo -Hermeregildo -Hermerinda -Hermes -Hermesinda -Hermesindo -Hermila -Hermilo -Hermina -Herminda -Hermindo -Hermine -Herminia -Herminio -Herminsul -Hermita -Hermitas -Hermogenes -Hermosinda -Hermoso -Hernan -Hernando -Hernani -Herney -Herodes -Heroina -Heron -Herschel -Hershel -Herta -Hertha -Herve -Hesham -Hester -Hettie -Heura -Heydi -Hiart -Hiba -Hibai -Hibernon -Hicham -Hichame -Hichan -Hichem -Hidaya -Hiedi -Hiedra -Hien -Higini -Higinia -Higinio -Hikaru -Hikmat -Hilal -Hilari -Hilaria -Hilarino -Hilario -Hilarion -Hilary -Hilda -Hilde -Hildebrando -Hildegard -Hildegarda -Hildegarde -Hildegart -Hildelisa -Hildred -Hillary -Hilma -Hilton -Hiltrud -Himad -Himar -Himilce -Himo -Hina -Hind -Hinda -Hinde -Hiniesta -Hipolit -Hipolita -Hipolito -Hira -Hiram -Hiroko -Hiromi -Hiroshi -Hirune -Hisako -Hisashi -Hiscio -Hisham -Hitesh -Hitomi -Hitos -Hiurma -Hizan -Hlal -Hlalia -Hlima -Hmad -Hmama -Hmida -Hmidou -Hnia -Hoa -Hobert -Hocine -Hoda -Hodaifa -Hodei -Hodeia -Holanda -Holga -Holger -Holi -Holley -Holli -Hollie -Hollis -Holly -Holmes -Homar -Homer -Homero -Hommad -Homobono -Honesta -Honesto -Honey -Hong -Hongbin -Hongbo -Hongfen -Hongjie -Hongjun -Hongli -Hongliang -Hongmei -Hongwei -Hongxia -Hongyan -Hongying -Hongyu -Honorat -Honorata -Honorato -Honoria -Honorina -Honorinda -Honorino -Honorio -Hontanares -Hoover -Hope -Horace -Horacia -Horacio -Horatio -Horatiu -Horia -Horiya -Hormisdas -Hornjoserbsce -Horst -Hortencia -Hortense -Hortensi -Hortensia -Hortensio -Hosain -Hosam -Hosanna -Hosea -Hosein -Hosni -Hosnia -Hossain -Hossam -Hossein -Hossnia -Houari -Houaria -Houcein -Houcin -Houcine -Houda -Houdaifa -Houmad -Hoummad -Hoummada -Hounaida -Houria -Houriya -Hourya -Housam -Housna -Housni -Housnia -Houssain -Houssaine -Houssam -Houssame -Houssein -Housseine -Houssien -Houssin -Houssine -Houssnia -Houston -Houyam -Hovhannes -Hovik -Howard -Hoyt -Hoz -Hra -Hrachya -Hrafn -Hrant -Hripsime -Hristian -Hristina -Hristinka -Hristiyan -Hristiyana -Hristo -Hristofor -Hristov -Hryhoriy -Hsiu -Hssain -Hssaine -Hu -Hua -Huamei -Huan -Huang -Huanhuan -Huanping -Huascar -Huaying -Huber -Hubert -Huberto -Hubertus -Huc -Huda -Hudaifa -Hue -Huerta -Huertas -Huerto -Huey -Hug -Hugh -Hugo -Hugues -Huguette -Hui -Huifang -Huifen -Huihui -Huili -Huilin -Huiling -Huimin -Huiping -Huiqin -Huixin -Huiying -Huizhen -Hulda -Huma -Humaima -Humaira -Humayun -Humbelina -Humbert -Humberta -Humberto -Humildad -Humilde -Hunaida -Hung -Hunter -Huong -Hur -Huria -Husam -Husein -Huseyin -Husnain -Hussain -Hussam -Hussein -Hussnain -Hutman -Hwa -Hyacinth -Hye -Hyman -Hyo -Hyon -Hyun -Hyusein -Iñake -Iñaki -Iñaky -Iñaqui -Iñigo -I?Aki -I?Igo -Ia -Iacob -Iacopo -Iacov -Iago -Iagoba -Iaia -Iain -Iakes -Iakov -Ian -Iana -Iancu -Ianire -Ianko -Ianos -Iara -Iaroslav -Iaroslava -Iashoda -Iasmina -Iasone -Ib -Iba -Ibai -Iballa -Iban -Ibana -Ibane -Ibay -Iber -Iberia -Ibernalo -Ibeth -Ibis -Ibo -Ibolya -Ibon -Ibone -Ibor -Ibou -Ibra -Ibrahem -Ibrahim -Ibrahima -Ibrahime -Ibrahin -Ibrahym -Ibraim -Ibraima -Ibrain -Ibrar -Ibryam -Ibtihal -Ibtisam -Ibtissam -Ibtissame -Ibtissem -Ica -Icar -Icaro -Ichrak -Icia -Iciar -Ico -Icram -Ida -Idafe -Idaira -Idalba -Idali -Idalia -Idalina -Idalmis -Idaly -Idan -Idania -Idara -Idayra -Iddrisu -Idelfonsa -Idelfonso -Idelia -Idelina -Idelisa -Idell -Idella -Idiatou -Idilia -Idilio -Idir -Idoia -Idolina -Idoya -Idrees -Idris -Idriss -Idrissa -Idrissia -Idurre -Idy -Ieda -Ieltxu -Iera -Ierai -Ieronim -Iesha -Ieva -Ievgen -Ievgenii -Ievgeniia -Ifara -Ifeanyi -Iffat -Ifigenia -Iftikhar -Igancio -Igarki -Ignaci -Ignacia -Ignacio -Ignas -Ignasi -Ignasia -Ignat -Ignazio -Igon -Igone -Igor -Igoris -Igors -Igotz -Ihab -Ihar -Ihara -Ihdih -Ihintza -Ihor -Ihsan -Ihsane -Ihssan -Ihssane -Ijaz -Ijlal -Ikbal -Ike -Ikechukwu -Iker -Ikerne -Ikhlas -Ikhlass -Ikhlef -Ikram -Ikrame -Ikran -Ila -Ilah -Ilai -Ilan -Ilana -Ilargi -Ilaria -Ilario -Ilarion -Ilazki -Ilda -Ildara -Ildefons -Ildefonsa -Ildefonso -Ildelfonso -Ildiko -Ilduara -Ileana -Ileen -Ilena -Ilene -Ilenia -Ilenuta -Ilham -Ilhame -Ilhan -Ilhem -Ilia -Ilian -Iliana -Iliane -Ilias -Iliass -Iliasse -Ilidia -Ilidio -Ilie -Ilies -Iliev -Ilieva -Ilina -Ilinca -Ilinka -Ilir -Ilisca -Iliuta -Iliya -Iliyan -Iliyana -Iliyas -Ilja -Ilka -Ilko -Illa -Illah -Illan -Illana -Illart -Illia -Illya -Ilma -Ilona -Ilonka -Ilsa -Ilse -Iluminacion -Iluminada -Iluminado -Ilune -Ilya -Ilyan -Ilyas -Ilyass -Ilyasse -Ilyes -Ilze -Ima -Imad -Imade -Iman -Imane -Imanol -Imar -Imara -Imelda -Imeldo -Imen -Imene -Imerio -Imma -Immacolata -Immaculada -Immanuel -Imobach -Imogene -Imperio -Imram -Imran -Imrane -Imre -Imtiaz -In -Ina -Inacio -Inaki -Inam -Inar -Inara -Inas -Inass -Inasse -Inaxio -Inaya -Inazio -Inca -Indalecia -Indalecio -Indalencio -Indar -Indara -Inderjit -Indhara -Indhira -India -Indiana -Indiara -Indira -Indra -Indre -Inell -Ines -Inesa -Inese -Ineso -Inessa -Ineta -Ineva -Inez -Inga -Inge -Ingeborg -Ingeburg -Inger -Ingo -Ingresar -Ingrid -Ingrida -Inha -Inhar -Inka -Inko -Inma -Inmaculada -Inmanol -Inna -Innocent -Inoa -Inocencia -Inocencio -Inocenta -Inocente -Insa -Insaf -Inssaf -Inti -Intisar -Intissar -Intza -Invencion -Ioachim -Ioan -Ioana -Ioanna -Ioannis -Ioar -Ioel -Iokin -Iola -Iolanda -Iomara -Ion -Iona -Ionatan -Ione -Ionel -Ionela -Ionelia -Ionica -Ionita -Ionut -Iordache -Iordan -Iordana -Iordanka -Ioritz -Ioseb -Ioseba -Iosif -Iosu -Iosua -Iosune -Ioulia -Iouri -Iovana -Iozsef -Ipar -Iqbal -Iqra -Ira -Iracema -Irache -Iraci -Iradi -Iragartze -Irai -Iraia -Iraida -Iraide -Iraides -Iraima -Irais -Iraitz -Irakli -Iram -Irama -Iran -Irani -Irania -Irantzu -Iranzu -Irasema -Irati -Iratxe -Iratze -Iraultza -Iray -Iraya -Irea -Irela -Irema -Iren -Irena -Irene -Irenea -Ireneo -Ireneu -Ireneusz -Irenia -Irenio -Irfan -Iria -Irian -Iriana -Iride -Irimia -Irina -Irine -Irinea -Irinel -Irineo -Iriome -Iris -Irish -Irkus -Irlanda -Irma -Irmantas -Irmgard -Irmina -Irmtraud -Iron -Ironim -Irshad -Iruña -Iru -Irundina -Irune -Irvin -Irving -Irwin -Iryna -Isa -Isaac -Isaak -Isabel -Isabela -Isabelina -Isabelino -Isabell -Isabella -Isabelle -Isabelo -Isac -Isacia -Isacio -Isadora -Isael -Isai -Isaia -Isaiah -Isaias -Isak -Isam -Isamael -Isamar -Isamara -Isamel -Isard -Isart -Isatou -Isaura -Isauro -Isbel -Iscle -Isel -Isela -Iset -Isha -Ishac -Ishak -Ishaq -Ishfaq -Ishrat -Ishtar -Ishtiaq -Ishwar -Isiah -Isicio -Isidor -Isidora -Isidoro -Isidra -Isidre -Isidro -Isilda -Isis -Iskander -Iskra -Iskren -Isla -Islam -Islenska -Isma -Ismaail -Ismael -Ismaela -Ismahan -Ismahane -Ismail -Ismaila -Ismary -Ismelda -Ismene -Ismenia -Ismet -Isobel -Isoken -Isolda -Isolde -Isolina -Isolino -Isona -Isora -Isra -Israa -Israe -Israel -Israr -Isreal -Isrrael -Issa -Issac -Issaga -Issah -Issaka -Issam -Issame -Issiaka -Issmail -Issouf -Istvan -Istvanne -Isusko -Itahisa -Itai -Italia -Italiano -Italo -Itamar -Itciar -Ithaisa -Ithan -Ithaysa -Itiel -Itohan -Itoitz -Itsasne -Itsaso -Itto -Itxaro -Itxaropena -Itxasne -Itxaso -Itxiar -Itzal -Itzan -Itzel -Itziar -Iu -Iudistira -Iudita -Iulen -Iulia -Iulian -Iuliana -Iulica -Iuliia -Iuliu -Iulius -Iune -Iurdana -Iurgi -Iuri -Iurie -Iurii -Iusra -Iustin -Iustina -Iva -Ivailo -Ivalin -Ivalina -Ivan -Ivana -Ivanca -Ivane -Ivaneide -Ivanete -Ivani -Ivania -Ivanichka -Ivanilda -Ivanilde -Ivanildo -Ivanka -Ivanna -Ivano -Ivanov -Ivanova -Ivar -Ivaylo -Ivelin -Ivelina -Ivelisse -Iver -Ives -Ivet -Iveta -Ivete -Iveth -Ivett -Ivette -Ivey -Ivie -Ivis -Ivo -Ivon -Ivona -Ivone -Ivonete -Ivonne -Ivor -Ivory -Ivy -Iwona -Ixai -Ixchel -Ixeia -Ixeya -Ixiar -Ixone -Iyad -Iyan -Iyana -Iza -Izabel -Izabela -Izabella -Izadi -Izaga -Izai -Izam -Izamar -Izan -Izana -Izar -Izara -Izarbe -Izarne -Izaro -Izarra -Izascun -Izaskum -Izaskun -Izei -Izeia -Izel -Izetta -Izhan -Izhar -Iziar -Izkander -Izola -Izolda -Izortze -Izotz -Izza -Izzan -J -Ja -Jaafar -Jabbar -Jaber -Jabier -Jabir -Jabran -Jacalyn -Jacek -Jacelyn -Jaciara -Jacinda -Jacint -Jacinta -Jacinto -Jacira -Jack -Jackelin -Jackeline -Jackelyn -Jacki -Jackie -Jacklyn -Jackqueline -Jackson -Jacky -Jaclyn -Jacob -Jacoba -Jacobina -Jacobo -Jacobus -Jacomar -Jacopo -Jacqualine -Jacque -Jacquelin -Jacquelina -Jacqueline -Jacquelyn -Jacquelyne -Jacquelynn -Jacques -Jacquetta -Jacqui -Jacquie -Jacquiline -Jacquline -Jacqulyn -Jad -Jada -Jadduch -Jade -Jaden -Jader -Jadiel -Jadilla -Jadiya -Jadiyetu -Jadwiga -Jae -Jael -Jafar -Jafet -Jaganatha -Jagdeep -Jagdish -Jagjit -Jagoba -Jagtar -Jahaira -Jahangir -Jahel -Jai -Jaime -Jaimee -Jaimeta -Jaimie -Jaimina -Jainaba -Jaione -Jair -Jaira -Jairo -Jaiver -Jake -Jakelin -Jakeline -Jakes -Jakob -Jakobe -Jakub -Jalal -Jaled -Jaleesa -Jalid -Jalil -Jalila -Jalisa -Jallal -Jama -Jamaa -Jamaal -Jamaica -Jamal -Jamar -Jame -Jamee -Jamel -James -Jamey -Jami -Jamie -Jamika -Jamil -Jamila -Jamile -Jamileth -Jamina -Jamison -Jammie -Jamshaid -Jamshed -Jan -Jana -Janae -Janaina -Janan -Janat -Janay -Jandira -Jandro -Jane -Janean -Janee -Janeen -Janel -Janell -Janella -Janelle -Janene -Janessa -Janet -Janeta -Janete -Janeth -Janett -Janetta -Janette -Janey -Jani -Janice -Janie -Janiece -Janin -Janina -Janine -Janira -Janire -Janis -Janise -Janita -Janitz -Janka -Jankey -Janko -Jann -Janna -Jannat -Janne -Jannet -Janneth -Jannett -Jannette -Jannie -Janny -Jano -Janos -Janosne -Janto -January -Janusz -Jany -Janyce -Jaouad -Jaouhara -Jaquelin -Jaquelina -Jaqueline -Jaquelyn -Jara -Jare -Jared -Jarein -Jarek -Jarischandra -Jarmila -Jarnail -Jaro -Jarod -Jaromir -Jaroslav -Jaroslava -Jaroslaw -Jarred -Jarrett -Jarrod -Jarvis -Jasbir -Jashim -Jasim -Jaskaran -Jasleen -Jasmeen -Jasmin -Jasmina -Jasmine -Jasminka -Jasna -Jason -Jasone -Jaspal -Jasper -Jaspreet -Jassim -Jasvinder -Jasvir -Jaswant -Jaswinder -Jatin -Jatinder -Jatri -Jauad -Jaume -Jaunita -Javad -Javaid -Javed -Javer -Javi -Javid -Javier -Javiera -Jawad -Jay -Jaya -Jayden -Jaydy -Jaye -Jayme -Jaymie -Jayna -Jayne -Jayone -Jayro -Jayson -Jazael -Jazmin -Jazmina -Jazmine -Jc -Jean -Jean-François -Jeana -Jeane -Jeanelle -Jeanene -Jeaneth -Jeanett -Jeanetta -Jeanette -Jeanice -Jeanie -Jeanine -Jeanmarie -Jeanna -Jeanne -Jeanneth -Jeannetta -Jeannette -Jeannie -Jeannine -Jed -Jeferson -Jeff -Jefferey -Jefferson -Jeffery -Jeffie -Jeffrey -Jeffry -Jefrey -Jefry -Jehan -Jehova -Jehu -Jeison -Jeisson -Jekaterina -Jelena -Jelloul -Jema -Jemaa -Jemal -Jemima -Jemina -Jemma -Jen -Jena -Jenae -Jenara -Jenaro -Jene -Jeneba -Jenee -Jenel -Jenell -Jenelle -Jenette -Jeneva -Jeni -Jenica -Jenice -Jenifer -Jeniffer -Jenina -Jenine -Jenise -Jenna -Jennefer -Jennell -Jennette -Jenni -Jennie -Jennifer -Jenniffer -Jennine -Jenny -Jennyfer -Jeno -Jens -Jeny -Jenyfer -Jerai -Jerald -Jeraldine -Jeramy -Jeray -Jere -Jeremi -Jeremiah -Jeremias -Jeremie -Jeremy -Jeri -Jerica -Jerico -Jerilyn -Jerlene -Jermaine -Jerobel -Jeroen -Jerold -Jerome -Jeromy -Jeroni -Jeronia -Jeronima -Jeronimo -Jerrell -Jerri -Jerrica -Jerrie -Jerrod -Jerrold -Jerry -Jerson -Jerusa -Jerusalen -Jerzy -Jesabel -Jese -Jesenia -Jeshua -Jesica -Jesika -Jesper -Jess -Jesse -Jessenia -Jessi -Jessia -Jessica -Jessie -Jessika -Jessy -Jessyca -Jestine -Jesua -Jesualda -Jesualdo -Jesus -Jesusa -Jesusita -Jetta -Jette -Jettie -Jetzabel -Jevgenij -Jevgenija -Jewel -Jewell -Jeyson -Jezabel -Jhan -Jhaneth -Jhenifer -Jhenny -Jhoan -Jhoana -Jhoanna -Jhoel -Jhon -Jhonatan -Jhonathan -Jhonattan -Jhonier -Jhonnatan -Jhonny -Jhony -Jhoselin -Jhoselyn -Jhosua -Jhovana -Ji -Jia -Jiachen -Jiacheng -Jiafu -Jiahao -Jiahui -Jiajia -Jiajie -Jiajing -Jiajun -Jiale -Jiali -Jialiang -Jialin -Jialing -Jiaming -Jian -Jianan -Jianbin -Jianbo -Jiancheng -Jianchun -Jiandong -Jiane -Jianfang -Jianfei -Jianfen -Jianfeng -Jianfu -Jiang -Jianguang -Jianguo -Jianhai -Jianhao -Jianhong -Jianhua -Jianhui -Jiani -Jianjing -Jianjun -Jiankang -Jianli -Jianliang -Jianlin -Jianling -Jianmei -Jianmin -Jianming -Jianping -Jianqiang -Jianqin -Jianqing -Jianqun -Jianrong -Jiansheng -Jianwei -Jianwen -Jianwu -Jianxiao -Jianxin -Jianxing -Jianxiong -Jianyan -Jianyi -Jianying -Jianyong -Jianyu -Jianyun -Jianzhen -Jianzhong -Jiao -Jiaqi -Jiaqian -Jiarui -Jiasheng -Jiawei -Jiawen -Jiaxiang -Jiaxin -Jiayi -Jiayin -Jiaying -Jiayu -Jie -Jieke -Jihad -Jihan -Jihane -Jilali -Jill -Jillali -Jillian -Jim -Jim Boyd -Jimena -Jimeno -Jimmie -Jimmy -Jimy -Jin -Jina -Jinan -Jinane -Jinbao -Jinbin -Jinbo -Jinchai -Jincheng -Jincui -Jine -Jinfen -Jinfeng -Jinfu -Jing -Jingjing -Jinguo -Jingwei -Jingwen -Jingyi -Jinhai -Jinhao -Jinhong -Jinhua -Jinhui -Jinjie -Jinjin -Jinlan -Jinli -Jinlian -Jinliang -Jinling -Jinlong -Jinmei -Jinming -Jinny -Jinping -Jinrong -Jinsheng -Jinsong -Jinwei -Jinxia -Jinxing -Jinyan -Jinying -Jinyong -Jinyu -Jinyun -Jinzhu -Jiri -Jitendra -Jitka -Jmiaa -Jo -Joachim -Joachin -Joakim -Joakin -Joakina -Joan -Joana -Joane -Joanes -Joanie -Joann -Joanna -Joanne -Joannes -Joannie -Joao -Joaozinho -Joaquim -Joaquima -Joaquin -Joaquina -Joar -Job -Jobita -Jocabed -Jocelyn -Jocelyne -Jochen -Jodee -Jodi -Jodie -Jody -Joe -Joeann -Joel -Joella -Joelle -Joellen -Joelma -Joerg -Joesph -Joetta -Joette -Joey -Joffre -Jofre -Joga -Joginder -Johan -Johana -Johann -Johanna -Johanne -Johannes -Johanny -Johara -Johathan -John -Johna -Johnatan -Johnathan -Johnathon -Johnattan -Johnetta -Johnette -Johnie -Johnna -Johnnatan -Johnnie -Johnny -Johnsie -Johnson -Johny -Joi -Joice -Joie -Joita -Jojo -Jokiñe -Jokin -Jolanda -Jolanta -Joleen -Jolene -Jolie -Joline -Jolita -Jolyn -Jolynn -Jon -Jona -Jonadab -Jonah -Jonahtan -Jonai -Jonalyn -Jonan -Jonas -Jonatan -Jonatas -Jonathan -Jonathon -Jonattan -Jonay -Jone -Jonell -Jonelle -Jones -Jong -Jonhatan -Joni -Jonie -Jonna -Jonnatan -Jonnathan -Jonnie -Jonny -Jony -Joost -Jorda -Jordan -Jordana -Jordi -Jordina -Jordon -Jordy -Jorel -Jorg -Jorge -Jorgelina -Jorgen -Jorgina -Joris -Joritz -Jorja -Jorje -Jorn -Jorunn -Josafat -Jose -Joseane -Joseba -Josebe -Josee -Josef -Josefa -Josefina -Josefine -Josefino -Joselia -Joselin -Joselina -Joseline -Joselito -Joselyn -Joselyne -Josemaria -Josep -Josepa -Joseph -Josephe -Josephina -Josephine -Josette -Josetxo -Josh -Joshua -Joshue -Josiah -Josiane -Josias -Josie -Josilene -Josimar -Joslyn -Jospeh -Josphine -Josselyn -Josu -Josua -Josue -Josuha -Josune -Joudia -Jounaida -Joussef -Jovan -Jovana -Jovani -Jovanka -Jovanna -Jovanny -Jovelyn -Jovina -Jovino -Jovita -Jovito -Joxe -Joy -Joya -Joyce -Joycelyn -Joye -Jozef -Jozefa -Jozsef -Jozsefne -Juan -Juana -Juanelo -Juani -Juanita -Juanito -Juanjo -Juanma -Juanmei -Juaquin -Juaquina -Jubal -Jucelia -Jucilene -Juda -Judas -Jude -Judhit -Judi -Judie -Judiht -Judit -Judita -Judite -Judith -Judson -Judy -Jue -Juergen -Jugatx -Juhani -Jula -Jule -Julee -Julen -Julene -Jules -Juli -Julia -Julian -Juliana -Juliane -Juliann -Julianna -Julianne -Juliano -Julie -Julieann -Julien -Julienne -Juliet -Julieta -Julieth -Julietta -Juliette -Julija -Julio -Julisa -Julissa -Julita -Julito -Julius -Julud -July -Jumaria -Jun -Juna -Junaid -Junbo -Juncal -June -Junfen -Junfeng -Jung -Junhao -Junhua -Junia -Junie -Junior -Junita -Junjie -Junkal -Junko -Junlei -Junli -Junliang -Junmei -Juno -Junping -Junquera -Junwei -Junxi -Junxiang -Junyan -Junyi -Junyong -Junyu -Juozas -Juraj -Jurate -Jurdan -Jurdana -Jurema -Jurg -Jurgen -Jurgi -Jurgita -Juri -Jurijs -Jurijus -Juris -Jussara -Just -Justa -Justas -Justice -Justin -Justina -Justinas -Justine -Justiniana -Justiniano -Justino -Justo -Justyna -Jutta -Juvenal -Juvencia -Juvencio -Juventina -Juventino -Jyoti -Jytte -Ka -Kaba -Kabbour -Kabir -Kabira -Kacem -Kacey -Kaci -Kacie -Kacper -Kacy -Kada -Kadda -Kaddour -Kaddy -Kader -Kadia -Kadialy -Kadiatou -Kadidia -Kadidiatou -Kadija -Kadijatou -Kadir -Kadrie -Kady -Kaede -Kahina -Kai -Kaiet -Kaila -Kailash -Kainat -Kaira -Kais -Kaitlin -Kaitlyn -Kaixin -Kaja -Kakha -Kakhaber -Kala -Kaleem -Kaleigh -Kaley -Kali -Kalid -Kalidasa -Kalidou -Kalifa -Kalil -Kalilou -Kalilu -Kalin -Kalina -Kalinka -Kallie -Kaloyan -Kalsoom -Kaltoum -Kaltouma -Kalyn -Kam -Kamal -Kamala -Kamalatmika -Kamalia -Kamaljit -Kamar -Kamel -Kamelia -Kameliya -Kamen -Kami -Kamil -Kamila -Kamilah -Kamile -Kamilia -Kamilla -Kamla -Kamo -Kamran -Kamrul -Kanchan -Kandace -Kande -Kandeh -Kandela -Kandi -Kandice -Kandis -Kandra -Kandy -Kane -Kaneez -Kanesha -Kang -Kangwei -Kanisha -Kanta -Kanwal -Kanza -Kaori -Kaotar -Kaoussou -Kaoutar -Kaouthar -Kapil -Kapila -Kapka -Kara -Karam -Karamat -Karamba -Karamjit -Karamo -Karamoko -Karan -Karapet -Kardama -Kare -Kareem -Kareen -Karel -Karelia -Karem -Karen -Karena -Karey -Kari -Karie -Karim -Karima -Karime -Karin -Karina -Karine -Karisa -Karishma -Karissa -Karl -Karla -Karle -Karleen -Karlene -Karlheinz -Karlo -Karlos -Karlota -Karly -Karlyn -Karma -Karmel -Karmele -Karmelo -Karmen -Karnail -Karol -Karola -Karole -Karolina -Karoline -Karolis -Karoly -Karolyn -Karon -Karren -Karri -Karrie -Karry -Karsten -Karttikeya -Kary -Karyl -Karyn -Karyna -Kasandra -Kasem -Kasey -Kasha -Kashif -Kashmir -Kasi -Kasie -Kasper -Kassandra -Kassem -Kassie -Kassim -Kassoum -Katalin -Katalina -Katarina -Katarzyna -Kate -Katelin -Katelyn -Katelynn -Katerin -Katerina -Katerine -Kateryna -Kathaleen -Katharina -Katharine -Katharyn -Kathe -Katheleen -Katherin -Katherina -Katherine -Kathern -Katheryn -Kathey -Kathi -Kathia -Kathie -Kathleen -Kathlene -Kathline -Kathlyn -Kathrin -Kathrine -Kathryn -Kathryne -Kathy -Kathya -Kathyrn -Kati -Katia -Katiana -Katiane -Katiba -Katie -Katina -Katiusca -Katiuscia -Katiuska -Katixa -Katja -Katlyn -Katrice -Katrien -Katrin -Katrina -Katrine -Katsiaryna -Kattalin -Kattie -Katty -Katy -Katya -Kauan -Kaur -Kausar -Kautar -Kauzar -Kavi -Kavita -Kawsu -Kawtar -Kawthar -Kay -Kayce -Kaycee -Kaye -Kayla -Kaylee -Kayleen -Kayleigh -Kaylene -Kazi -Kazimiera -Kazimierz -Kazuko -Kbir -Kbira -Ke -Keanu -Keba -Kebba -Kebir -Kebira -Kecia -Kedara -Kedarnath -Keeley -Keely -Keena -Keenan -Kees -Keesha -Keiko -Keila -Keira -Keisha -Keisy -Keita -Keith -Keitha -Keke -Keli -Kelia -Kelian -Kelle -Kellee -Kelley -Kelli -Kellie -Kelly -Kellye -Kelsey -Kelsi -Kelsie -Keltoum -Keltouma -Keltse -Kelvin -Kely -Kemal -Kemberly -Kemen -Kemo -Kemoko -Kemuel -Ken -Kena -Kenai -Kenan -Kenay -Kenda -Kendal -Kendall -Kendra -Kendrick -Keneth -Kenia -Kenisha -Kenji -Kenna -Kennedy -Kenneth -Kennith -Kenny -Kent -Kenton -Kenya -Kenyatta -Kenyetta -Kenza -Kenzo -Keoma -Kepa -Kera -Keren -Kerena -Keri -Kerman -Kermit -Kerri -Kerrie -Kerry -Kerstin -Kesha -Keshia -Kesia -Kestutis -Ketevan -Ketty -Keturah -Keva -Keven -Kevin -Kevyn -Kewal -Kewin -Kexin -Keyla -Keyra -Khachatur -Khachik -Khaddouj -Khaddouja -Khadem -Khader -Khadi -Khadidiatou -Khadidja -Khadidjetou -Khadija -Khadijah -Khadijatou -Khadijetou -Khadim -Khadime -Khadir -Khadiya -Khadjou -Khadouj -Khadouja -Khadra -Khadre -Khady -Khadyja -Khaira -Khaldia -Khaled -Khalid -Khalida -Khalifa -Khalil -Khalilah -Khalissa -Khaly -Khamis -Khamiss -Khamissa -Khammar -Khamsa -Khamssa -Khan -Khaoula -Khardiata -Khatia -Khatima -Khatir -Khatri -Khatuna -Khawar -Khawla -Khdija -Khedidja -Kheir -Kheira -Kheireddine -Khelifa -Khira -Khitam -Khizar -Khlifa -Khlifia -Khnata -Kholoud -Khoudia -Khouloud -Khrystyna -Khuram -Khurram -Khurshid -Kia -Kian -Kiana -Kiara -Kiera -Kieran -Kiersten -Kiesha -Kieth -Kike -Kiko -Kiley -Kiliam -Kilian -Killian -Kilyan -Kim -Kima -Kimber -Kimberely -Kimberlee -Kimberley -Kimberli -Kimberlie -Kimberly -Kimbery -Kimbra -Kimera -Kimetz -Kimi -Kimiko -Kin -Kina -Kinda -Kindra -Kine -King -Kinga -Kingsley -Kintuillang -Kinza -Kiova -Kiowa -Kip -Kira -Kiran -Kirara -Kirby -Kirenia -Kiriam -Kirian -Kiril -Kirilka -Kirill -Kirilov -Kirk -Kiro -Kirpal -Kirsa -Kirsten -Kirsti -Kirstie -Kirstin -Kirsty -Kiryl -Kisha -Kishore -Kishwar -Kissima -Kit -Kittie -Kitty -Kiyoko -Kizkitza -Kizzie -Kizzy -Kjell -Klaas -Klara -Klaudia -Klaus -Klavdiya -Kleber -Klever -Knarik -Knut -Koba -Koen -Kofi -Koichi -Koji -Koldo -Koldobika -Koldobike -Koly -Kolyo -Komal -Konate -Kone -Konimba -Konrad -Konstantin -Konstantina -Konstantine -Konstantinos -Kontxi -Kora -Koraima -Korey -Kori -Korka -Kornelia -Korneliya -Koro -Kortney -Koruko -Kory -Kosta -Kostadin -Kostadinka -Kostadinov -Kostas -Kostiantyn -Kostyantyn -Kouider -Kourtney -Kousar -Kraig -Krasen -Krasimir -Krasimira -Krassimir -Krassimira -Krassimire -Krastina -Kremena -Kris -Krish -Krishan -Krishna -Krissy -Krista -Kristal -Kristan -Kristeen -Kristel -Kristen -Kristi -Kristian -Kristiana -Kristie -Kristin -Kristina -Kristine -Kristiyan -Kristle -Kristof -Kristofer -Kristoffer -Kristopher -Kristy -Kristyn -Kristyna -Krisztian -Krisztina -Krum -Krysta -Krystal -Krystel -Krysten -Krystian -Krystin -Krystina -Krystle -Krystyna -Krzystof -Krzysztof -Ksenia -Kseniia -Ksenija -Kseniya -Kuldeep -Kuldip -Kuljit -Kulvir -Kulwant -Kulwinder -Kum -Kumar -Kumba -Kumiko -Kun -Kunal -Kundalini -Kunti -Kurt -Kurtis -Kwabena -Kwadwo -Kwaku -Kwame -Kwasi -Kyara -Kyla -Kyle -Kylee -Kylian -Kylie -Kym -Kymberly -Kyoko -Kyong -Kyra -Kyrylo -Kyung -L -Laarbi -Laaroussi -Laaziz -Laaziza -Laban -Lacey -Lachelle -Lachezar -Lachman -Laci -Lacie -Lacramioara -Lacresha -Lacrimioara -Lacy -Lada -Ladawn -Ladisla -Ladislaa -Ladislada -Ladislao -Ladislau -Ladislav -Ladji -Ladonna -Lady -Lael -Laetitia -Lafdil -Lah -Lahasen -Lahat -Lahbib -Lahbiba -Lahcen -Lahcene -Lahoma -Lahouari -Lahouaria -Lahoucine -Lahoussine -Lahsen -Lahssan -Lahssen -Lai -Laia -Laiba -Laid -Laida -Laie -Laiene -Laila -Laima -Laimonas -Laimute -Lain -Laina -Laine -Laira -Lais -Laisa -Lajos -Lajuana -Lakbir -Lakbira -Lakeesha -Lakeisha -Lakendra -Lakenya -Lakesha -Lakeshia -Lakhbir -Lakhdar -Lakhvir -Lakhwinder -Lakia -Lakiesha -Lakisha -Lakita -Lal -Lala -Lali -Lalia -Lalit -Lalla -Lama -Lamar -Lamara -Lamarana -Lambert -Lamberta -Lamberto -Lambertus -Lamia -Lamiaa -Lamiae -Lamiita -Lamin -Lamina -Lamine -Lamiri -Lamis -Lamkadem -Lamnouar -Lamonica -Lamont -Lamya -Lamyaa -Lamyae -Lan -Lana -Lance -Lancelot -Lancine -Landelina -Landelino -Lander -Landing -Landon -Lane -Lanell -Lanelle -Lanette -Lanfen -Lang -Lani -Lanie -Lanita -Lanmei -Lannie -Lanny -Lanora -Lansana -Lanxiang -Lanying -Laquanda -Laquita -Lara -Larabi -Larae -Laraine -Larbi -Laree -Larhonda -Larion -Larisa -Larissa -Larita -Laritza -Laro -Laronda -Larosi -Larraine -Larraitz -Larry -Lars -Larue -Larysa -Lasana -Lasandra -Lasha -Lashanda -Lashandra -Lashaun -Lashaunda -Lashawn -Lashawna -Lashawnda -Lashay -Lashell -Lashon -Lashonda -Lashunda -Lasonya -Lassana -Lasse -Lassina -Lassine -Lastenia -Laszlo -Laszlone -Latanya -Latarsha -Latasha -Latashia -Latesha -Latia -Laticia -Latif -Latifa -Latina -Latinka -Latino -Latisha -Latonia -Latonya -Latoria -Latosha -Latoya -Latoyia -Latrice -Latricia -Latrina -Latrisha -Latviešu -Lau -Laudelina -Laudelino -Laudiceia -Laudina -Laudino -Launa -Laura -Lauralee -Lauran -Laure -Laurea -Laureana -Laureano -Laureen -Laurel -Lauren -Laurena -Laurence -Laurencia -Laurencio -Laurene -Laurens -Laurent -Laurenta -Laurentia -Laurentina -Laurentino -Laurentiu -Laurentzi -Lauretta -Laurette -Lauri -Laurice -Laurie -Laurinda -Laurindo -Laurine -Lauro -Lauryn -Laurynas -Lautaro -Lavada -Lavelle -Lavenia -Lavera -Lavern -Laverna -Laverne -Laveta -Lavette -Lavina -Lavinia -Lavon -Lavona -Lavonda -Lavone -Lavonia -Lavonna -Lavonne -Lawana -Lawanda -Lawanna -Lawerence -Lawrence -Laxman -Laxmi -Laya -Layachi -Laye -Layla -Layne -Layonel -Layra -Lazar -Lazara -Lazaro -Laziz -Laziza -Le -Lea -Leah -Lealdina -Lean -Leana -Leandra -Leandre -Leandro -Leann -Leanna -Leanne -Leanora -Leatha -Leatrice -Lebdaoui -Lech -Lecia -Leda -Ledicia -Ledy -Lee -Leeann -Leeanna -Leeanne -Leena -Leendert -Leer -Leesa -Legarda -Lehbib -Lei -Leia -Leida -Leide -Leidiane -Leidy -Leidys -Leif -Leigh -Leigha -Leighann -Leila -Leilani -Leilei -Leira -Leire -Leisa -Leisha -Leisy -Leixuri -Leiza -Leize -Leizuri -Lekbir -Lekbira -Lekisha -Lela -Lelah -Leland -Lele -Lelia -Lemine -Lemuel -Len -Lena -Lenard -Lene -Leni -Lenia -Lenin -Lenita -Lenka -Lenna -Lennart -Lennie -Lennon -Lenny -Lenora -Lenore -Lenut -Lenuta -Leny -Leo -Leocadia -Leocadio -Leocricia -Leocricio -Leodegario -Leola -Leoma -Leomar -Leon -Leona -Leonard -Leonarda -Leonardo -Leonardus -Leonas -Leoncia -Leoncio -Leone -Leonel -Leonela -Leonhard -Leonia -Leonice -Leonid -Leonida -Leonidas -Leonides -Leonido -Leonie -Leonila -Leonilda -Leonilde -Leonilo -Leonisa -Leonor -Leonora -Leonore -Leonte -Leontin -Leontina -Leontine -Leopold -Leopolda -Leopoldina -Leopoldo -Leora -Leota -Leovigilda -Leovigildo -Lera -Lerida -Lerma -Leroy -Les -Lesa -Lesbia -Lesha -Lesia -Leslee -Lesley -Lesli -Leslie -Lesly -Lesme -Lesmes -Lessie -Lester -Lesya -Leszek -Leta -Letha -Leticia -Letisha -Letitia -Letizia -Lettie -Letty -Lev -Levan -Levani -Levent -Levente -Levi -Levon -Lewis -Lexie -Lexuri -Leya -Leyanis -Leyda -Leydi -Leydis -Leyi -Leyla -Leyre -Lezlie -Lhachmi -Lhassan -Lhassane -Lhou -Lhouceine -Lhoucine -Lhoussain -Lhoussaine -Lhousseine -Lhoussine -Li -Lia -Liam -Lian -Liana -Liane -Lianet -Liang -Liangfeng -Liangjun -Liangliang -Lianlian -Lianna -Lianne -Lianying -Liaqat -Libardo -Libasse -Libbie -Libby -Libe -Liber -Liberada -Liberata -Liberato -Liberia -Liberio -Liberta -Libertad -Liberto -Liberty -Libia -Libin -Libor -Liboria -Liborio -Librada -Librado -Lica -Licas -Licer -Liceria -Licerio -Licesio -Licet -Liceth -Lichun -Licia -Licinia -Licinio -Lida -Lidan -Lide -Lider -Lidia -Lidiana -Lidiane -Lidice -Lidiia -Lidija -Lidio -Lidiya -Lidon -Lidong -Liduina -Liduvina -Lidya -Lie -Lien -Liena -Liene -Lier -Lierni -Lies -Liesbeth -Lieselotte -Lifang -Lifen -Lifeng -Liga -Ligaya -Ligia -Ligita -Liguang -Liguo -Liher -Lihong -Lihua -Liisa -Lijana -Lijiao -Lijie -Lijin -Lijing -Lijuan -Lijun -Lika -Lila -Lili -Lilia -Liliam -Lilian -Liliana -Liliane -Lilianne -Lilibeth -Lilica -Liliia -Liling -Lilioara -Lilit -Lilith -Liliya -Lilla -Lilli -Lillia -Lilliam -Lillian -Lilliana -Lillie -Lilly -Lily -Lilya -Lilyana -Lima -Limam -Liman -Limber -Limberg -Limbert -Limei -Limin -Liming -Limpia -Lin -Lina -Linarejos -Linas -Lincoln -Linda -Lindalva -Lindaura -Lindomar -Lindsay -Lindsey -Lindsy -Lindy -Line -Linet -Linette -Linfeng -Ling -Lingfen -Lingfeng -Lingjun -Lingli -Lingling -Lingmiao -Lingwei -Lingxiao -Lingyan -Lingyong -Lingyu -Lingyun -Lingzhen -Lingzhi -Lingzhu -Linh -Linjie -Linlin -Linn -Linna -Linnea -Linnie -Lino -Linos -Linsey -Linus -Linwei -Linwood -Lioba -Lionel -Lior -Lioubov -Lioudmila -Liping -Liqin -Liqing -Liqun -Lira -Liria -Lirio -Lirios -Lirong -Lis -Lisa -Lisabeth -Lisandra -Lisandro -Lisard -Lisarda -Lisardo -Lisbel -Lisbet -Lisbeth -Lise -Liselotte -Liset -Liseth -Lisett -Lisette -Lisha -Lisheng -Lissa -Lisset -Lissete -Lisseth -Lissett -Lissette -Lissy -Lita -Liu -Liuba -Liubov -Liudmila -Liudmyla -Liuva -Liv -Livan -Livia -Livino -Livio -Livioara -Liviu -Livius -Liwei -Liwen -Lixia -Lixiang -Lixiao -Lixin -Lixiong -Liya -Liyan -Liying -Liyong -Liyu -Liyun -Liz -Liza -Lizabeth -Lizandra -Lizandro -Lizar -Lizardo -Lizbet -Lizbeth -Lizer -Lizet -Lizeth -Lizette -Lizhen -Lizhong -Lizhu -Lizi -Lizica -Lizzette -Lizzie -Ljiljana -Ljubica -Llacolen -Llanos -Llara -Llarina -Llatzer -Lledo -Lleir -Lleonard -Llibert -Llibertat -Llorenç -Llorens -Lloyd -Lluc -Lluch -Llucia -Lluis -Lluisa -Llum -Lluna -Llura -Lluvia -Loaira -Loan -Loana -Loay -Lobna -Lofti -Logan -Lohitzune -Lohizune -Loic -Loida -Loinaz -Lois -Loise -Lokapala -Lokman -Lola -Loles -Loli -Lolimar -Lolita -Loma -Lon -Lona -Londa -Lone -Long -Longina -Longino -Longinos -Loni -Lonna -Lonnie -Lonny -Lope -Lora -Loraine -Loralee -Lorand -Lore -Lorea -Lorean -Loredana -Loree -Loreen -Lorelay -Lorelei -Loreley -Lorella -Loren -Lorena -Lorene -Lorenz -Lorenza -Lorenzo -Loreta -Loreto -Loretta -Lorette -Lorgio -Lori -Loria -Loriann -Lorie -Lorien -Lorilee -Lorina -Lorinda -Lorine -Loris -Lorita -Lorna -Lorraine -Lorretta -Lorri -Lorriane -Lorrie -Lorrine -Lory -Lot -Lotfi -Lothar -Lotte -Lottie -Lou -Louann -Louanne -Louay -Louazna -Loubna -Louella -Louetta -Louhou -Louie -Louis -Louisa -Louise -Louiza -Lounes -Loura -Lourd -Lourdes -Lourenco -Lourie -Loutfi -Louvenia -Love -Lovella -Lovely -Lovepreet -Loveth -Lovetta -Lovie -Lowell -Loyce -Loyd -Loyda -Loyola -Lu -Lua -Luali -Luan -Luana -Luann -Luanna -Luanne -Luar -Luara -Luay -Luba -Lubica -Lubna -Lubomir -Lubos -Luc -Luca -Lucas -Lucca -Lucelia -Lucelly -Lucely -Lucena -Lucero -Lucette -Luci -Lucia -Lucian -Luciana -Luciane -Luciano -Lucica -Lucidia -Lucidio -Lucie -Lucien -Luciene -Lucienne -Lucila -Lucile -Lucilene -Lucilia -Luciliano -Lucilla -Lucille -Lucilo -Lucimar -Lucimara -Lucina -Lucinda -Lucindo -Lucineia -Lucineide -Lucinia -Lucinio -Lucio -Lucita -Lucitania -Lucius -Lucky -Lucrecia -Lucrecio -Lucretia -Lucrezia -Lucy -Lucyna -Ludek -Ludibia -Ludie -Ludivia -Ludivina -Ludivine -Ludivino -Ludmila -Ludmilla -Ludovic -Ludovica -Ludovico -Ludovina -Ludwig -Ludwing -Lue -Luella -Luetta -Luftolde -Luigi -Luigia -Luigina -Luis -Luisa -Luisana -Luise -Luisina -Luiz -Luiza -Lujan -Luka -Lukas -Lukasz -Luke -Luken -Lukene -Lukman -Lula -Lulu -Luminita -Luna -Lupe -Lupercio -Lupicina -Lupicinia -Lupicinio -Lupita -Lupu -Luqman -Lur -Lura -Lurdes -Lurlene -Lurline -Luscinda -Lusia -Lusine -Lutgarda -Lutgardo -Luther -Lutz -Luvenia -Luxi -Luz -Luzdivina -Luzdivino -Luzia -Luzinete -Luzmila -Luztolde -Luzviminda -Lya -Lyazid -Lyda -Lydia -Lydie -Lyes -Lyla -Lyle -Lyman -Lyn -Lyna -Lynda -Lyndia -Lyndon -Lyndsay -Lyndsey -Lynell -Lynelle -Lynetta -Lynette -Lynn -Lynna -Lynne -Lynnette -Lynsey -Lynwood -Lys -Lyuba -Lyuben -Lyubka -Lyubomir -Lyubomira -Lyubomyr -Lyubomyra -Lyubov -Lyudmil -Lyudmila -Lyudmyla -M -M'Barek -M'Bark -M'Barka -M'Hamed -M'Hammed -M'Hand -Ma -Maaike -Maamar -Maanan -Maarten -Maati -Maazouza -Mabel -Mabelle -Mable -Mabrouk -Mabrouka -Mac -Macarena -Macaria -Macario -Macedon -Macedonia -Macedonio -Machelle -Macia -Maciana -Macie -Maciej -Macire -Mack -Mackenzie -Macoumba -Macrina -Macrino -Mactar -Macy -Madai -Madalen -Madalena -Madalene -Madalin -Madalina -Madaline -Madalyn -Madani -Maday -Maddalen -Maddalena -Maddi -Maddie -Maddox -Madelaine -Madelein -Madeleine -Madelen -Madelene -Madeleyne -Madelin -Madeline -Madelyn -Mademba -Madge -Madhu -Madhusudan -Madi -Madicke -Madie -Madiha -Madina -Madiop -Madison -Madjid -Madlen -Madlena -Madlyn -Madona -Madonna -Madou -Madrona -Mady -Mae -Maeba -Maegan -Mael -Maelle -Maelys -Maeva -Maeve -Mafalda -Maffeo -Maftei -Magali -Magalie -Magalis -Magaly -Magalys -Magan -Magaret -Magatte -Magda -Magdala -Magdalen -Magdalena -Magdalene -Magdaleno -Magdalina -Magdolna -Magela -Magen -Magencia -Magencio -Maggie -Maghnia -Magi -Magin -Magina -Magna -Magne -Magno -Magnolia -Magnus -Magomed -Maguette -Magueye -Magyar -Maha -Mahah -Mahalia -Maham -Mahamadi -Mahamadou -Mahamadu -Mahamed -Mahammed -Mahamud -Mahassine -Mahayub -Mahayuba -Mahboob -Mahdi -Mahdia -Mahdjouba -Maheen -Maher -Mahesh -Mahfoud -Mahfud -Mahi -Mahieddine -Mahir -Mahjoub -Mahjouba -Mahmood -Mahmoud -Mahmud -Mahnoor -Mahy -Mahyub -Mahyuba -Mai -Maia -Maialen -Maiane -Maiara -Maica -Maico -Maicol -Maicon -Maida -Maider -Maier -Maija -Maik -Maika -Maike -Maikel -Maikol -Maila -Maile -Mailen -Mailin -Mailyn -Maimona -Maimoun -Maimouna -Maimuna -Maiol -Maiquel -Maira -Maire -Mairena -Maisa -Maisaa -Maisae -Maisha -Maisie -Maissa -Maissae -Maitane -Maite -Maiteder -Maitena -Maixa -Maj -Maja -Majd -Majda -Majdouline -Majed -Majid -Majida -Major -Majorie -Maka -Makan -Makeda -Makha -Makhan -Makhlouf -Makhtar -Maki -Makiko -Makki -Makram -Maksim -Maksims -Maksym -Maksymilian -Malaika -Malainin -Malainine -Malak -Malake -Malam -Malamin -Malamine -Malang -Malaquias -Malco -Malcolm -Malcom -Malek -Malen -Malena -Maleni -Malgorzata -Mali -Malia -Malica -Malick -Malik -Malika -Maliki -Malin -Malina -Malinalli -Malinda -Malinka -Malisa -Malissa -Malka -Malkeet -Malkhaz -Malkit -Malle -Mallie -Mallory -Malorie -Malte -Malva -Malvina -Malwina -Mama -Mamadi -Mamadou -Mamadu -Mamady -Mamasa -Mame -Mamerta -Mamerto -Mames -Mamia -Mamie -Mamma -Mammat -Mammie -Mamoudou -Mamoun -Mamoune -Mamour -Mamourou -Mamoutou -Mamta -Mamudou -Mamudu -Mamuka -Mamun -Man -Mana -Manahil -Manal -Manale -Manana -Manar -Manasa -Manases -Manda -Mandeep -Mandi -Mandiaye -Mandica -Mandie -Manding -Mandita -Mandy -Mane -Manea -Manel -Manela -Manex -Manfred -Manfredo -Mangal -Maniang -Manie -Maninder -Manish -Manisha -Manja -Manjeet -Manjinder -Manjit -Manjot -Manju -Mankilef -Mannana -Manoel -Manoela -Manoj -Manol -Manola -Manole -Manolete -Manoli -Manolita -Manolo -Manon -Manpreet -Manrique -Mansata -Mansoor -Mansor -Mansour -Mansoura -Manssour -Mansur -Mantas -Manu -Manual -Manue -Manuel -Manuela -Manuele -Many -Manzoor -Mao -Maodo -Mapenda -Maple -Maqbool -Maqsood -Marçal -Mar -Mara -Maragaret -Maragda -Maragret -Marah -Maram -Marame -Maranda -Mararia -Marat -Maravilla -Maravillas -Maraya -Marbelis -Marbella -Marbelys -Marc -Marca -Marcas -Marce -Marcel -Marcela -Marcelene -Marceli -Marceliana -Marceliano -Marcelin -Marcelina -Marceline -Marcelino -Marcell -Marcella -Marcelle -Marcelli -Marcello -Marcellus -Marcelo -Marcene -Marchelle -Marci -Marcia -Marcial -Marciala -Marciana -Marciano -Marcie -Marcilene -Marcin -Marcio -Marcionila -Marck -Marco -Marcolina -Marcolino -Marcos -Marcu -Marcus -Marcy -Mardell -Mare -Mareike -Marek -Mareme -Maren -Marena -Marene -Marey -Marfil -Marg -Marga -Margalida -Margara -Margaret -Margareta -Margarete -Margareth -Margaretha -Margarethe -Margarett -Margaretta -Margarette -Margarida -Margarit -Margarita -Margarite -Margarito -Margart -Margaryta -Margaux -Marge -Margene -Margeret -Margert -Margery -Marget -Margherita -Margie -Margit -Margita -Margo -Margorie -Margot -Margoth -Margret -Margrett -Margrit -Marguerita -Marguerite -Margurite -Margy -Marharyta -Marhta -Mariña -Mariñe -Mari -Maria -Mariagrazia -Mariah -Mariam -Mariama -Mariame -Mariami -Mariamu -Marian -Mariana -Mariane -Marianela -Marianella -Mariangel -Mariangela -Mariangeles -Marianita -Mariann -Marianna -Marianne -Marianny -Mariano -Mariasol -Mariasun -Mariateresa -Mariatou -Maribel -Maribeth -Marica -Maricarmen -Maricel -Maricela -Maricica -Maricielo -Maricris -Maricruz -Maricuta -Marie -Mariea -Marieke -Mariel -Mariela -Marielena -Mariella -Marielle -Mariely -Mariem -Marieme -Mariemma -Marien -Marieta -Marietou -Marietta -Mariette -Marife -Mariflor -Mariia -Marija -Marijana -Marijke -Marika -Mariko -Marilda -Marilee -Marilena -Marilene -Marilenny -Marili -Marilia -Marilin -Marilina -Marillac -Marilo -Marilou -Marilu -Mariluz -Marilyn -Marilynn -Marilza -Marimar -Marin -Marina -Marinalva -Marinda -Marine -Marineide -Marinel -Marinela -Marinella -Marines -Marinete -Marinette -Marinica -Marinka -Marino -Marinov -Marinus -Mario -Marioara -Mariola -Marioly -Marion -Mariona -Mariora -Maripaz -Maris -Marisa -Marisabel -Marisca -Marise -Marisel -Marisela -Marisha -Marisol -Marissa -Maristela -Marit -Marita -Marite -Marites -Maritxell -Maritxu -Maritza -Mariuca -Marius -Mariusz -Mariuta -Mariuxi -Marivel -Marivi -Marivic -Mariya -Mariyam -Mariyan -Mariyana -Mariyka -Mariza -Marizabel -Marizete -Marizol -Marja -Marjan -Marjatta -Marjolein -Marjorie -Marjory -Mark -Markel -Marketa -Marketta -Markita -Marko -Markos -Markus -Marla -Marlana -Marleen -Marlen -Marlena -Marlene -Marleni -Marlenis -Marlenny -Marleny -Marley -Marli -Marlies -Marliese -Marlin -Marline -Marlis -Marlise -Marlo -Marlon -Marlucia -Marly -Marlyn -Marlys -Marna -Marni -Marnie -Maroa -Maroan -Maroua -Marouan -Marouane -Marquerite -Marquetta -Marquina -Marquis -Marquita -Marquitta -Marry -Marsel -Marsha -Marshall -Marta -Marth -Martha -Marthe -Martiño -Marti -Martijn -Martim -Martin -Martina -Martine -Martinho -Martiniana -Martiniano -Martino -Martins -Martinus -Martir -Martires -Martiria -Martirian -Martirio -Marton -Martos -Martxel -Marty -Martyn -Martyna -Martynas -Martzel -Marua -Maruan -Maruja -Marusia -Marusya -Maruxa -Marva -Marvel -Marvella -Marvin -Marvis -Marwa -Marwan -Marwane -Marx -Mary -Marya -Maryalice -Maryam -Maryama -Maryame -Maryan -Maryana -Maryann -Maryanna -Maryanne -Marybel -Marybelle -Marybeth -Maryellen -Maryem -Maryetta -Maryia -Maryjane -Maryjo -Maryland -Marylee -Marylene -Marylin -Maryline -Maryln -Marylou -Marylouise -Maryluz -Marylyn -Marylynn -Maryna -Maryori -Maryory -Maryrose -Maryse -Marysol -Maryuri -Maryury -Maryvonne -Marzena -Marzia -Marzio -Marzok -Marzouk -Masako -Masha -Masiel -Mason -Masood -Masoud -Massaer -Massamba -Massar -Masse -Masseye -Massiel -Massimiliano -Massimo -Massin -Massinissa -Massira -Massoud -Masud -Masum -Mata -Matar -Matas -Mate -Matea -Matei -Matej -Matene -Matenin -Mateo -Mateos -Mateu -Mateus -Mateusz -Matha -Matheo -Matheus -Mathew -Mathias -Mathieu -Mathilda -Mathilde -Mathis -Mati -Matias -Maties -Matilda -Matilde -Matrikas -Matt -Matteo -Matthew -Matthias -Matthieu -Matti -Mattia -Mattias -Mattie -Mattin -Maturino -Matus -Matutina -Matvey -Matxalen -Maty -Maud -Maude -Maudie -Maudilia -Maudilio -Maura -Maureen -Mauren -Mauri -Maurice -Maurici -Mauricia -Mauricio -Maurilia -Maurilio -Maurina -Maurine -Maurino -Maurita -Maurizio -Mauro -Maverick -Mavis -Mawdo -Max -Maxence -Maxi -Maxie -Maxim -Maxima -Maxime -Maximiana -Maximiano -Maximilia -Maximilian -Maximiliana -Maximiliano -Maximina -Maximino -Maximo -Maxine -Maxwell -Maxym -May -Maya -Mayalen -Mayara -Maybell -Maybelle -Mayca -Maycon -Mayda -Maye -Mayela -Mayelin -Mayerli -Mayerlin -Mayerling -Mayerly -Mayi -Mayka -Maykel -Mayla -Maylen -Maylin -Mayme -Maynard -Mayo -Mayola -Mayoro -Mayra -Maysa -Maysaa -Maysae -Maysam -Mayssa -Mayssae -Mayte -Mayumi -Mayya -Mazatl -Mazen -Mazhar -Mazie -Mbacke -Mballo -Mbarak -Mbarek -Mbark -Mbarka -Mbaye -Mbemba -Mbene -Mckenzie -Mckinley -Md -Meagan -Meaghan -Mecedes -Mechelle -Mechthild -Mecias -Meda -Medarda -Medardo -Medea -Medin -Medina -Medir -Medou -Medoune -Mee -Meena -Meg -Megan -Meggan -Meghan -Meghann -Megumi -Mehak -Mehamed -Mehboob -Mehdi -Mehdia -Mehmed -Mehmet -Mehmood -Mehran -Mehrdad -Mehwish -Mei -Meie -Meifang -Meifen -Meifeng -Meihong -Meihua -Meijuan -Meijun -Meike -Meilan -Meili -Meilian -Meiling -Meimei -Meiqin -Meire -Meirong -Meiryan -Meissa -Meixia -Meixiang -Meiyan -Meiying -Meiyu -Meiyun -Meizhen -Meizhu -Mekka -Mekki -Mel -Melaine -Melani -Melania -Melanie -Melanio -Melannie -Melanny -Melany -Melanya -Melba -Melca -Melchor -Melchora -Melcion -Melcior -Melda -Melecia -Melecio -Melek -Melia -Melibea -Melida -Melina -Melinda -Meline -Melisa -Melissa -Melissia -Melita -Melitina -Melito -Meliton -Melitona -Meliza -Mellie -Mellisa -Mellissa -Melodee -Melodi -Melodia -Melodie -Melody -Melonie -Melony -Melquiades -Melquisedec -Melva -Melvi -Melvin -Melvina -Melvy -Melvyn -Melynda -Mena -Menana -Mencey -Mencia -Mendia -Mendy -Meng -Mengmeng -Mengting -Mengyao -Mennana -Merçe -Merab -Merari -Merce -Merced -Mercedes -Mercedez -Mercedita -Mercenario -Merche -Mercy -Meredith -Merete -Meri -Meriam -Meriame -Merian -Merida -Merideth -Meridith -Meriem -Merieme -Merien -Merilyn -Merissa -Merita -Meritxel -Meritxell -Meriyem -Merle -Merlene -Merlin -Merlinda -Merlita -Merly -Merlyn -Merna -Merouane -Merri -Merrie -Merrilee -Merrill -Merry -Mertie -Mertixell -Mertxe -Mervin -Mervyn -Mery -Meryam -Meryama -Meryem -Meryeme -Meryen -Meryl -Merzak -Merzouk -Mesalina -Mesbah -Mesias -Messaoud -Messaouda -Messoud -Meta -Metin -Metodi -Metodio -Mette -Mezian -Meziane -Mfaddal -Mfedal -Mfeddal -Mfedla -Mhamed -Mhammad -Mhammed -Mhand -Mher -Mi -Mia -Mian -Miao -Miaomiao -Mica -Micael -Micaela -Micah -Micha -Michael -Michaela -Michaele -Michail -Michal -Michale -Micheal -Michel -Michela -Michelangelo -Michele -Michelina -Micheline -Michell -Michelle -Michiel -Michiko -Mickael -Mickey -Micki -Mickie -Micol -Midiala -Midori -Mieczyslaw -Miesha -Migdalia -Migel -Migle -Miglena -Mignon -Miguel -Miguela -Miguelangel -Miguelina -Mihael -Mihaela -Mihai -Mihaiela -Mihail -Mihaila -Mihails -Mihaita -Mihalache -Mihaly -Miho -Mijael -Mijail -Mika -Mikael -Mikaela -Mikalai -Mikayel -Mike -Mikel -Mikelats -Mikeldi -Mikele -Mikhail -Mikhaylo -Mikheil -Miki -Mikkel -Mikki -Miklos -Mikola -Mikolaj -Mila -Milad -Milada -Miladin -Miladis -Milady -Miladys -Milagritos -Milagro -Milagros -Milagrosa -Milan -Milana -Milca -Milcho -Milciades -Milda -Mildred -Mildrey -Miledy -Miledys -Mileidy -Mileidys -Milen -Milena -Milene -Milenka -Miles -Milesio -Miley -Mileydis -Milford -Milian -Milica -Milissa -Milka -Milko -Millan -Millana -Millaray -Millard -Miller -Millerlandy -Millicent -Millie -Milly -Milo -Milorad -Milos -Miloslav -Miloud -Milouda -Miloudi -Milton -Milud -Miluda -Miluta -Milva -Milvia -Mima -Mimi -Mimon -Mimona -Mimoun -Mimouna -Mimoune -Mimount -Mimun -Mimuna -Mimunt -Mimuntz -Min -Mina -Minata -Mincho -Minda -Mindaugas -Mindi -Mindy -Mine -Minel -Minerva -Minervina -Minervino -Ming -Minghao -Minghua -Minghui -Mingliang -Mingming -Minh -Minia -Minjie -Minka -Minko -Minna -Minnie -Minodora -Minta -Minying -Mioara -Miodrag -Miorita -Miqueas -Miquel -Miquela -Mir -Mira -Mirabela -Miracle -Miranda -Mirari -Mircea -Mircho -Mirco -Mireia -Mireilla -Mireille -Mireira -Mirel -Mirela -Mirella -Mirelys -Miren -Mirena -Mirene -Mirentxu -Mireya -Miria -Miriam -Mirian -Miriela -Miriem -Mirjam -Mirjana -Mirka -Mirko -Mirna -Miro -Miron -Miroslav -Miroslava -Miroslaw -Miroslawa -Mirsad -Mirta -Mirtha -Miruna -Miryam -Miryan -Mirza -Misael -Misbah -Misericordia -Misha -Mishel -Mishell -Misho -Miss -Missoum -Missy -Misti -Mistie -Misty -Misu -Mita -Mitch -Mitchel -Mitchell -Mitica -Mitka -Mitko -Mitkov -Mitra -Mitrita -Mitsue -Mitsuko -Mittie -Mitxel -Mitzi -Mitzie -Miyoko -Miyuki -Mizanur -Mjid -Mladen -Moaad -Moacir -Moad -Moazzam -Moctar -Modest -Modesta -Modestas -Modesto -Modi -Modibo -Modou -Mody -Mofadal -Mofaddal -Mofaddala -Mogens -Moh -Moha -Mohamad -Mohamadi -Mohamadou -Mohamed -Mohamedi -Mohamedou -Mohammad -Mohammadi -Mohammadine -Mohammed -Mohan -Mohand -Mohatar -Mohcin -Mohcine -Mohinder -Mohit -Mohsan -Mohsen -Mohsin -Mohsine -Mohssen -Mohssin -Mohssine -Mohtar -Moira -Moise -Moises -Mokhtar -Mokhtaria -Mollie -Molly -Moloud -Momar -Momath -Momchil -Momna -Momodou -Mona -Monaim -Monalisa -Moncef -Monday -Moneiba -Moner -Monet -Moneyba -Monfrague -Monia -Monica -Monico -Monika -Monike -Monina -Monique -Monir -Monnie -Monroe -Monsalud -Monsef -Monserra -Monserrat -Monserrata -Monserrate -Monsif -Monssef -Mont -Montaña -Montañas -Monte -Montemayor -Montes -Montesclaros -Montesion -Montevirgen -Montiel -Montsant -Montse -Montserrat -Monty -Monya -Moon -Mor -Mora -Morad -Moraima -Morelia -Morena -Moreno -Moreyba -Morgan -Morgana -Morgane -Moriah -Moriba -Moritz -Moro -Morris -Morro -Morten -Morteza -Morton -Moruena -Mory -Mosaab -Mose -Moses -Moshe -Moslim -Mossaab -Mostafa -Mostapha -Mostefa -Motserrat -Mouaad -Mouad -Mouctar -Moufida -Mouha -Mouhamadou -Mouhamed -Mouhamet -Mouhcin -Mouhcine -Mouhsin -Mouhsine -Mouhssin -Mouhssine -Moukhtar -Moulay -Moulaye -Mouloud -Moulouda -Mouloudi -Moumen -Moumna -Mouna -Mounaim -Mounia -Mounim -Mounina -Mounir -Mounira -Mounsef -Mounsif -Mountaga -Mounya -Mourad -Mourtalla -Mousa -Moussa -Moustafa -Moustapha -Mozell -Mozella -Mozelle -Mrabih -Mstislav -Muad -Mubarak -Mubashar -Mubashir -Mudasar -Mudassar -Muela -Mugurel -Muhamad -Muhamadou -Muhamed -Muhammad -Muhammadou -Muhammed -Mui -Mujahid -Mujer -Mukesh -Mukhtar -Mumtaz -Muna -Munawar -Muneeb -Munia -Munir -Muoi -Murad -Murat -Muriel -Murielle -Murilo -Murray -Murtaza -Musa -Musah -Musarrat -Mushegh -Mushtaq -Muskan -Muskilda -Muslim -Mussa -Mustafa -Mustansar -Mustapha -Muzaffar -My -Mya -Mycola -Myesha -Mykhailo -Mykhaylo -Mykola -Mykyta -Myla -Mylene -Myles -Myong -Myra -Myriam -Myrian -Myrl -Myrle -Myrna -Myron -Myroslav -Myroslava -Myrta -Myrtice -Myrtie -Myrtis -Myrtle -Myryam -Myung -Mzia -Na -Naaima -Naama -Naara -Nabar -Nabeel -Nabeela -Nabel -Nabi -Nabia -Nabiha -Nabil -Nabila -Nabor -Nacer -Nacera -Naceur -Nacho -Nacimiento -Nacira -Nacor -Nada -Nadal -Nadalina -Nadaya -Nadeem -Nadege -Nadejda -Nadene -Nader -Nadezda -Nadezhda -Nadi -Nadia -Nadifa -Nadiia -Nadim -Nadin -Nadina -Nadine -Nadir -Nadira -Nadiuska -Nadiya -Nadja -Nadjat -Nadjem -Nadjet -Nadjia -Nadjib -Nadka -Nadra -Nadya -Nadzeya -Naeem -Nael -Naema -Nafi -Nafisa -Nafissa -Nafissatou -Nagina -Nagore -Naha -Nahara -Nahed -Naheed -Nahia -Nahiane -Nahiara -Nahid -Nahida -Nahikari -Nahila -Nahim -Nahima -Nahir -Nahla -Nahomi -Nahuel -Nahum -Nahun -Naia -Naiala -Naiane -Naiara -Naida -Naihara -Naika -Naikari -Naike -Nail -Naila -Nailya -Naim -Naima -Naimi -Nain -Nair -Naira -Nairobi -Nais -Naiyara -Najah -Najam -Najat -Najate -Najem -Najet -Naji -Najia -Najib -Najiba -Najih -Najiha -Najim -Najima -Najiya -Najla -Najlaa -Najlae -Najma -Najoua -Najua -Najwa -Nakesha -Nakia -Nakisha -Nakita -Nakor -Nala -Nalaya -Nallely -Nallua -Nam -Nama -Namibia -Namory -Nan -Nana -Nancee -Nancey -Nanci -Nancie -Nancy -Nandi -Nandini -Nando -Nandor -Nanette -Nani -Nannette -Nannie -Nansi -Nanuli -Nao -Naoki -Naoko -Naoma -Naomi -Naomy -Naoual -Naouar -Naouel -Naoufal -Naoufel -Napoleon -Nara -Naray -Narayan -Narcis -Narcisa -Narciso -Narciza -Narda -Nardo -Nardy -Narek -Nareme -Naresh -Nargis -Nariman -Narimane -Narinder -Narine -Narjes -Narjis -Narjiss -Narjisse -Naroa -Naroba -Naryis -Nasar -Naseem -Naseer -Naser -Nasera -Nashira -Nasiha -Nasim -Nasima -Nasir -Nasira -Nasko -Nasly -Nasr -Nasra -Nasrallah -Nasreddine -Nasreen -Nasrin -Nasrine -Nasrullah -Nassar -Nasser -Nassera -Nassiba -Nassim -Nassima -Nassir -Nassira -Nast -Nastaca -Nastase -Nastasia -Nastia -Natacha -Natael -Natale -Natali -Natalia -Natalie -Nataliia -Natalija -Natalina -Natalio -Nataliya -Natalja -Natallia -Nataly -Natalya -Natan -Natanael -Nataniel -Nataraja -Natasa -Natascha -Natascia -Natasha -Natashia -Nataxa -Natela -Nath -Nathali -Nathalia -Nathalie -Nathaly -Nathan -Nathanael -Nathanial -Nathaniel -Nati -Natia -Natija -Natisha -Natividad -Nativitat -Natnael -Nato -Natosha -Natura -Natxo -Natzaret -Naual -Naufal -Naufel -Naum -Nauman -Nauset -Nausica -Nauzet -Navdeep -Naveed -Naveeda -Naveen -Navia -Navid -Navidad -Navil -Navila -Navin -Navjot -Navneet -Nawal -Nawar -Nawaz -Nawel -Nawfal -Naya -Nayab -Nayade -Nayala -Nayan -Nayana -Nayara -Nayat -Nayda -Nayden -Nayeli -Nayely -Nayem -Nayhara -Nayib -Nayibe -Nayim -Nayima -Nayin -Nayla -Nayma -Nayomi -Nayra -Nayua -Nazakat -Nazam -Nazan -Nazar -Nazare -Nazarena -Nazareno -Nazaret -Nazareth -Nazaria -Nazarica -Nazarii -Nazario -Nazariy -Nazha -Nazia -Nazih -Naziha -Nazik -Nazim -Nazir -Nazira -Nazma -Nazmie -Nazmul -Nazrul -Ndeye -Ndiaga -Ndiasse -Ndiaye -Ndiogou -Ndiouga -Ndongo -Neagu -Neal -Nebil -Nebojsa -Neco -Necole -Nector -Neculae -Neculai -Neculina -Ned -Neda -Nedal -Nedas -Nedelcho -Nederlands -Nedka -Nedko -Nedra -Nedyalka -Nedyalko -Neelam -Neeltje -Neely -Neeraj -Nefer -Nefertari -Neftali -Neha -Nehad -Nehemias -Nehuen -Neiba -Neida -Neide -Neidy -Neil -Neila -Neira -Neisa -Neisy -Neiva -Neizan -Nekal -Nekane -Nel -Nela -Nelba -Nelcy -Nelda -Nele -Nelea -Neli -Nelia -Nelida -Nelina -Nelita -Nell -Nella -Nelle -Nelli -Nellie -Nelly -Nellya -Nelo -Nelsa -Nelso -Nelson -Nelsy -Nelu -Neluta -Nelutu -Nelva -Nely -Nelya -Nemecia -Nemecio -Nemesia -Nemesio -Nemesis -Nena -Nenad -Nene -Nenita -Neo -Neoma -Neomi -Neonila -Nepomuceno -Nepomuk -Neptali -Nere -Nerea -Nereida -Nereo -Nereyda -Neri -Neria -Nerida -Nerijus -Neriman -Nerina -Neringa -Nerissa -Nermin -Nermine -Neron -Nery -Nerys -Neskutz -Nesrin -Nesrine -Nessrine -Nestor -Neta -Nettie -Neu -Neus -Neusa -Neuza -Neva -Nevada -Nevadita -Neven -Nevena -Nevenka -Neves -Neville -Newton -Ney -Neyda -Neyen -Neyla -Neysa -Neyva -Neyzan -Nezar -Nezha -Nfamara -Nga -Ngagne -Ngan -Ngoc -Ngone -Nguyet -Niño -Nia -Niall -Niama -Niamh -Niang -Nica -Nicandro -Nicanor -Nicanora -Nicasia -Nicasio -Niccolo -Nicefora -Niceforo -Nicereta -Nicesio -Niceta -Niceto -Nichel -Nichelle -Nichita -Nichol -Nichola -Nicholas -Nichole -Nicholle -Nick -Nicki -Nickie -Nickolas -Nickole -Nicky -Nico -Nicodemo -Nicodemus -Nicol -Nicola -Nicolaas -Nicolae -Nicolai -Nicolaie -Nicolas -Nicolasa -Nicolau -Nicolay -Nicole -Nicoleta -Nicoletta -Nicolette -Nicolina -Nicolita -Nicoll -Nicolle -Nicolo -Nicomeda -Nicomedes -Nicu -Niculae -Niculai -Niculaie -Niculina -Niculita -Nicusor -Nicuta -Nida -Nidae -Nidal -Nidia -Niels -Niesha -Nieva -Nieve -Nieves -Nigel -Nighat -Nihad -Nihal -Nihat -Nijole -Nika -Nikhil -Niki -Nikia -Nikita -Nikki -Niklas -Niko -Nikol -Nikola -Nikolaev -Nikolai -Nikolaj -Nikolajs -Nikolaos -Nikolas -Nikolaus -Nikolay -Nikole -Nikoleta -Nikolett -Nikoletta -Nikolina -Nikolinka -Nikolov -Nikoloz -Nikos -Nikte -Nil -Nila -Nilda -Nile -Nilo -Nils -Nilsa -Nilson -Nilton -Nilva -Nilza -Nima -Nimia -Nimra -Nina -Ninbe -Ninel -Nineta -Ninfa -Ning -Nini -Ninive -Nino -Ninoska -Niobe -Nira -Niria -Niriti -Nirmal -Nirmala -Nirmin -Nirmine -Nirvana -Nisa -Nisamar -Nisar -Nisha -Nishan -Nisma -Nisri -Nisrin -Nisrine -Nissrin -Nissrine -Nistor -Nit -Nita -Nitin -Niurka -Nivaldo -Nivardo -Nivaria -Nivia -Nixon -Nizam -Nizar -Noa -Noah -Noam -Noaman -Noara -Noble -Nobuko -Nodar -Noe -Noel -Noela -Noeli -Noelia -Noella -Noelle -Noema -Noemi -Noemia -Noemie -Noemy -Nofre -Nogaye -Noha -Nohaila -Nohara -Nohayla -Nohelia -Nohemi -Nohemy -Nohora -Noive -Nola -Nolan -Nolasco -Nolberta -Nolberto -Noli -Nolwenn -Noma -Noman -Nombre -Nona -Nonato -Nonia -Nonila -Nonita -Nonito -Nonna -Noor -Nor -Nora -Norah -Noraida -Noralba -Norayr -Norbert -Norberta -Norberto -Norbey -Norddin -Norddine -Nordi -Nordin -Nordine -Noreddin -Noreddine -Noredine -Noreen -Norene -Norhan -Noria -Norica -Norik -Noriko -Noriman -Norine -Noris -Norka -Norma -Norman -Normand -Normando -Norris -Nory -Nosa -Nosair -Nosakhare -Nosheen -Nouaman -Nouara -Noufal -Noufissa -Nouh -Nouha -Nouhad -Nouhaila -Nouhayla -Nouhou -Nouhoum -Nouhoun -Nouman -Noumidia -Nounout -Nour -Noura -Nouraddin -Nouraddine -Nouradine -Nourddin -Nourddine -Nourdin -Nourdine -Noureddin -Noureddine -Nouredin -Nouredine -Nourelhouda -Nourhan -Nouria -Nouriddin -Nourreddine -Nourredine -Nouzha -Nova -Novella -Nozha -Nuño -Nu -Nuala -Nube -Nubia -Nugzar -Nuha -Nuhacet -Nuhaila -Nuhazet -Numan -Numbers -Numidia -Nune -Nunilon -Nuno -Nunzia -Nunzio -Nur -Nura -Nuri -Nuria -Nuris -Nurul -Nury -Nurys -Nusrat -Nut -Nuta -Nuti -Nutu -Nuvia -Nydia -Nyima -Nyla -Nyuma -O -Oana -Obaida -Obdulia -Obdulio -Obed -Oceane -Oceania -Ocie -Octavi -Octavia -Octavian -Octaviana -Octaviano -Octavio -Oda -Odair -Odalis -Odalys -Odd -Oddvar -Odei -Odelia -Odelinda -Odell -Odessa -Odet -Odeta -Odete -Odette -Odila -Odile -Odilia -Odilio -Odilo -Odin -Odina -Odis -Odon -Odonila -Odulia -Ofelia -Ofelio -Ofir -Oghogho -Ognyan -Ohara -Ohiana -Ohiane -Oiana -Oiane -Oiartza -Oier -Oihan -Oihana -Oihane -Oiher -Oilda -Oinatz -Ok -Okacha -Oksana -Oktay -Ola -Olaf -Olai -Olaia -Olaitz -Olaiz -Olalla -Olallo -Olanda -Olarizu -Olas -Olatz -Olau -Olav -Olavo -Olay -Olaya -Olayo -Ole -Oleg -Olegaria -Olegario -Olegs -Oleguer -Oleh -Oleksandr -Oleksandra -Oleksii -Oleksiy -Olen -Olena -Olene -Olesea -Olesia -Olesya -Oleta -Olevia -Olexandr -Olexiy -Olga -Olguita -Olguta -Olha -Olida -Olimpia -Olimpio -Olimpiu -Olin -Olina -Olinda -Olinta -Oliva -Olive -Oliver -Olivera -Oliverio -Oliveros -Olivia -Olivier -Olivio -Oliviu -Olivo -Oliwia -Ollie -Olmedo -Olmo -Olof -Oltita -Olvido -Olya -Olympia -Om -Oma -Omaida -Omaima -Omaira -Omar -Omara -Omayma -Omayra -Omega -Omeima -Omer -Omid -Omkaltoum -Omkeltoum -Omnia -Ona -Onan -Ondina -Onditz -Ondiz -Ondrej -Oneida -Oneka -Onel -Onelia -Onelio -Onesiforo -Onesima -Onesimo -Onia -Onie -Onintza -Onintze -Onisim -Onisor -Onita -Onkar -Onofra -Onofre -Onorina -Onur -Opal -Ophelia -Ophelie -Oprea -Oprica -Ora -Oralee -Oralia -Orazio -Orbelinda -Oren -Orencia -Orencio -Orentina -Orentino -Orest -Oresta -Oreste -Orestes -Oretha -Oreto -Orfa -Orfelia -Orfelina -Orfilia -Orhan -Oria -Oriana -Orietta -Oriol -Orion -Oristela -Oristila -Orkatz -Orlanda -Orlando -Orlin -Orlinda -Orm -Ornela -Ornella -Oro -Orofila -Oroitz -Oroncio -Orosia -Orosio -Orpha -Orquidea -Orreaga -Orsolya -Ortansa -Ortensia -Ortzi -Orval -Orville -Orysya -Osagie -Osahon -Osama -Osane -Osaretin -Osariemen -Osaro -Osas -Osazee -Osazuwa -Osbaldo -Oscar -Osel -Osiris -Oskar -Oskia -Oskitz -Osman -Osmani -Osmany -Osmar -Osmel -Osmundo -Ossama -Ossie -Ostap -Osvaldo -Oswald -Oswaldo -Ot -Otar -Otelia -Otelo -Otger -Otha -Othman -Othmane -Otilia -Otilio -Otis -Otman -Otmane -Otmar -Otniel -Oto -Oton -Otoniel -Ottavio -Otto -Ou -Ouadia -Ouadie -Ouafa -Ouafaa -Ouafae -Ouafi -Ouafila -Ouahib -Ouahiba -Ouahid -Ouahida -Ouail -Ouali -Oualid -Ouarda -Ouardia -Ouasim -Ouasima -Ouassila -Ouassim -Ouassima -Ouazna -Ouiam -Ouiame -Ouida -Ouidad -Ouijdane -Ouisal -Ouissal -Ouissam -Ouiza -Oukacha -Oulaid -Oulaya -Oulimata -Oum -Oumaima -Oumama -Oumar -Oumarou -Oumayma -Oumhani -Oumkaltoum -Oumkeltoum -Oumnia -Oumou -Oumy -Oury -Ousainou -Ousama -Ousman -Ousmane -Oussama -Ousseynou -Outhmane -Outman -Outmane -Ove -Ovidi -Ovidia -Ovidio -Ovidiu -Owen -Oxana -Oxel -Oystein -Oyvind -Ozell -Ozella -Ozie -Ozlem -P -Pa -Paata -Pabla -Pablina -Pablino -Pablo -Paciana -Paciano -Paciencia -Pacifica -Pacifico -Pacita -Paco -Padma -Padua -Page -Paige -Pal -Palma -Palmer -Palmira -Palmiro -Paloma -Palomares -Palwinder -Pam -Pamala -Pamela -Pamelia -Pamella -Pamila -Pamula -Pan -Panagiota -Panagiotis -Panayot -Pancho -Pancracia -Pancracio -Pandava -Pandora -Pandu -Panfilo -Pankaj -Pansy -Pantaleon -Pantaleona -Paola -Paolina -Paolo -Papa -Pape -Paqui -Paquita -Paramjeet -Paramjit -Parasca -Paraschiv -Paraschiva -Parascovia -Pardeep -Pargat -Pariksit -Paris -Parker -Parmenia -Parmenio -Parminder -Parmjit -Parthenia -Particia -Parvati -Parveen -Parvez -Parvin -Parvinder -Parviz -Parvoleta -Parwinder -Paryania -Pascal -Pascale -Pascasia -Pascasio -Pascu -Pascua -Pascual -Pascuala -Pascualina -Pasion -Pasqual -Pasquale -Pasqualina -Pasqualino -Pastor -Pastora -Pasty -Pat -Pathe -Patience -Patria -Patric -Patrica -Patrice -Patrici -Patricia -Patricio -Patrick -Patrik -Patrina -Patrisia -Patrizia -Patrizio -Patrocinia -Patrocinio -Patrycia -Patrycja -Patryk -Patsy -Patti -Pattie -Patty -Patxi -Patxo -Pau -Paul -Paula -Paule -Paulene -Pauletta -Paulette -Pauli -Paulica -Paulin -Paulina -Pauline -Paulino -Paulita -Paulius -Paulo -Paulus -Paun -Pauna -Pavel -Pavla -Pavlin -Pavlina -Pavlinka -Pavlo -Pavol -Pawan -Pawel -Payal -Paz -Peña -Peñarroya -Peñas -Peñitas -Peace -Pearl -Pearle -Pearlene -Pearlie -Pearline -Pearly -Pedro -Pedrona -Peg -Pegerto -Peggie -Peggy -Pei -Peifen -Peijun -Peio -Peipei -Pekka -Pelagia -Pelagio -Pelayo -Pelegri -Pelegrin -Peli -Peligros -Pello -Pencho -Penda -Penelope -Peng -Pengcheng -Pengfei -Pengpeng -Penha -Penka -Penko -Penney -Penni -Pennie -Penny -Pep -Pepa -Pepe -Pepi -Pepita -Per -Percy -Pere -Peregrin -Peregrina -Peregrino -Perez -Perfecta -Perfecto -Pergentina -Pergentino -Perla -Perlita -Pernille -Perpetua -Perpetuo -Perry -Perseveranda -Persida -Peru -Pervaiz -Pervez -Petar -Pete -Peter -Petia -Petko -Petkov -Petr -Petra -Petrache -Petrana -Petranka -Petras -Petre -Petrea -Petri -Petria -Petrica -Petrina -Petrisor -Petro -Petrola -Petrona -Petronel -Petronela -Petronella -Petronila -Petronilo -Petronio -Petros -Petrov -Petrova -Petru -Petrus -Petrut -Petruta -Petter -Petya -Petyo -Phebe -Phil -Philip -Philipe -Philipp -Philippa -Philippe -Phillip -Phillis -Philomena -Philomina -Phoebe -Phung -Phuong -Phylicia -Phylis -Phyliss -Phyllis -Pia -Piadosa -Piedad -Piedade -Piedades -Piedraescrita -Piedras -Piedrasanta -Piedrasantas -Pier -Piera -Pierangelo -Piere -Piergiorgio -Pierina -Pierino -Pierluigi -Piero -Pierpaolo -Pierre -Pierrette -Pietat -Pieter -Pieternella -Pietro -Pilar -Pilare -Pili -Pinar -Pineda -Ping -Pingping -Pinkie -Pino -Pio -Piotr -Piper -Pipino -Piroska -Pius -Placentina -Placer -Placeres -Placid -Placida -Placidia -Placido -Plamen -Plamena -Plamenka -Platon -Plautila -Plinio -Poder -Pok -Pol -Pola -Poliana -Policarpa -Policarpio -Policarpo -Polidoro -Polina -Polivio -Polly -Pollyana -Polo -Polonia -Polonio -Polya -Pompei -Pompeu -Pompeya -Pompeyo -Pompilia -Pompilio -Pompiliu -Ponç -Poncho -Ponciana -Ponciano -Poncio -Pooja -Pool -Poonam -Pop -Popa -Porfidia -Porfidio -Porfiria -Porfirio -Porres -Porsche -Porsha -Portal -Porter -Portia -Potenciana -Potenciano -Poul -Poveda -Povilas -Prabhjot -Pradeep -Pradiumna -Prado -Prados -Prakash -Praxedes -Prazeres -Preben -Preciosa -Precious -Predestina -Predestinacion -Predrag -Prem -Premislao -Prepedigna -Presentacio -Presentacion -Presentina -Preslav -Preslava -Preston -Pricila -Pricilla -Prima -Primavera -Primiano -Primitiva -Primitivo -Primo -Prince -Princess -Prisca -Priscila -Priscilia -Prisciliana -Prisciliano -Priscilla -Priscilo -Prisco -Pristila -Pritivi -Priya -Priyanka -Procesa -Proceso -Procopia -Procopio -Proculo -Profira -Progreso -Promise -Prosper -Prospera -Prospero -Protasio -Providenci -Providencia -Prudence -Prudenci -Prudencia -Prudenciana -Prudenciano -Prudencio -Przemyslaw -Publio -Puebla -Puerto -Puig -Puiu -Pulaja -Pura -Pureza -Puri -Purificacio -Purificacion -Purisima -Purita -Pusa -Pushpa -Puy -Qadeer -Qaisar -Qaiser -Qamar -Qasim -Qasir -Qi -Qian -Qiana -Qiang -Qianqian -Qiao -Qiaoli -Qiaoling -Qiaoqiao -Qiaoyan -Qiaoyun -Qifeng -Qiming -Qin -Qing -Qingfeng -Qinghua -Qingqing -Qingxia -Qinqin -Qiong -Qiqi -Qiu -Qiuhua -Qiumei -Qiuping -Qiuyun -Quan -Queen -Queenie -Queila -Quentin -Queralt -Queren -Querubin -Querubina -Quiana -Quico -Quilen -Quiliano -Quim -Quima -Quin -Quinciano -Quincy -Quinidio -Quinn -Quino -Quinti -Quintiliana -Quintiliano -Quintin -Quintina -Quintino -Quinton -Quique -Quirico -Quirina -Quirino -Quirze -Quiteria -Quiterio -Qun -Qunfeng -Qunwei -Quyen -R -Rabab -Rababe -Rabah -Rabea -Rabeh -Rabha -Rabi -Rabia -Rabiaa -Rabie -Rabih -Rabii -Racha -Rachad -Rachael -Rachal -Racheal -Rached -Rachel -Rachele -Rachell -Rachelle -Rachid -Rachida -Rachyd -Racquel -Rada -Radames -Radek -Radha -Radhames -Radi -Radia -Radian -Radim -Radina -Radita -Radka -Radko -Radmila -Radomir -Radoslav -Radoslava -Radoslaw -Radostin -Radostina -Radouan -Radouane -Radovan -Radu -Raducu -Radut -Radwan -Radya -Rae -Raeann -Raed -Rael -Raelene -Rafa -Rafael -Rafaela -Rafaelina -Rafaella -Rafal -Rafaqat -Rafayel -Rafel -Rafela -Raffaela -Raffaele -Raffaella -Rafi -Rafia -Rafik -Rafika -Rafila -Rafiq -Raghbir -Raghu -Ragnar -Ragnhild -Raguel -Rahal -Rahama -Raheel -Raheela -Rahel -Rahela -Rahhal -Rahila -Rahim -Rahima -Rahma -Rahman -Rahmouna -Rahul -Rai -Raian -Raico -Raid -Raida -Raidel -Raihana -Raimon -Raimond -Raimonda -Raimondas -Raimondo -Raimund -Raimunda -Raimundas -Raimundo -Raina -Rainer -Rainiero -Raisa -Raissa -Raiza -Raj -Raja -Rajaa -Rajae -Rajan -Rajeev -Rajendra -Rajesh -Rajinder -Rajiv -Rajni -Raju -Rajwant -Rajwinder -Rakel -Rakesh -Rakia -Raksasa -Raleigh -Ralf -Ralitsa -Ralph -Raluca -Ram -Rama -Ramadan -Raman -Ramandeep -Ramata -Ramatoulaye -Ramaz -Ramazan -Rambha -Ramdan -Ramdane -Rameez -Ramesh -Rami -Ramia -Ramil -Ramin -Raminta -Ramir -Ramira -Ramiro -Ramiz -Ramon -Ramona -Ramoni -Ramonita -Ramos -Ramses -Ramunas -Ramune -Ramute -Ramy -Ramzan -Ramzi -Ran -Rana -Ranae -Ranbir -Randa -Randal -Randall -Randee -Randell -Randhir -Randi -Randolph -Randy -Ranee -Rangel -Rani -Rania -Ranim -Ranjeet -Ranjit -Ranses -Ranulfo -Ranya -Raouf -Raouia -Raoul -Raphael -Raphaela -Rapita -Raquel -Rares -Rasa -Rasha -Rashad -Rasheed -Rasheeda -Rashid -Rashida -Rashpal -Rasim -Rastislav -Ratiba -Rauda -Raudel -Rauf -Raul -Ravana -Raveca -Raven -Ravi -Ravinder -Rawan -Rawda -Ray -Raya -Rayan -Rayane -Raycho -Rayco -Raydel -Raye -Rayford -Rayhan -Rayhana -Rayisa -Rayko -Raylene -Raymon -Raymond -Raymonde -Raymunda -Raymundo -Rayna -Rayner -Raysa -Rayyan -Raza -Razak -Razan -Razia -Razika -Razmik -Razvan -Razzaq -Rbiha -Rea -Reagan -Reanna -Reatha -Reba -Rebbeca -Rebbecca -Rebeca -Rebecca -Rebecka -Rebeka -Rebekah -Rebekka -Recaredo -Recesvinto -Recuerdo -Reda -Redencion -Redha -Redosinda -Redouan -Redouane -Reduan -Redwan -Redwane -Reed -Reem -Reena -Refugia -Refugio -Regalada -Regalado -Regan -Regena -Regenia -Reggie -Reghina -Regiane -Regina -Reginald -Reginaldo -Regine -Reginia -Regino -Regis -Regla -Regula -Regulo -Rehab -Reham -Rehan -Rehana -Rehman -Reid -Reidar -Reidun -Reiko -Reimundo -Reina -Reinalda -Reinaldo -Reinel -Reinelda -Reiner -Reinerio -Reinhard -Reinhold -Reinier -Reis -Reita -Rejane -Reka -Rekha -Rekia -Relu -Rema -Remberto -Remco -Reme -Remedio -Remedios -Remei -Remi -Remigi -Remigia -Remigijus -Remigio -Remo -Remona -Remus -Remy -Remzi -Rena -Renae -Renaldo -Renan -Renat -Renata -Renatas -Renate -Renato -Renaud -Renay -Renda -Rene -Renea -Renee -Reneta -Renetta -Reni -Renier -Renita -Renjie -Renna -Renu -Renzo -Reposo -Resham -Reshma -Ressie -Restituta -Restituto -Resurreccion -Reta -Retha -Reto -Retta -Reuben -Reva -Revaz -Reveca -Rex -Rey -Reyad -Reyes -Reyhan -Reyita -Reymundo -Reyna -Reynalda -Reynaldo -Reynier -Reza -Rezki -Rhea -Rheba -Rhett -Rhiannon -Rhimo -Rhimou -Rhita -Rhizlane -Rhoda -Rhona -Rhonda -Rhut -Rhys -Ria -Riad -Riahi -Riana -Riansares -Riasat -Riaz -Ribana -Ribera -Ribhu -Rica -Ricard -Ricarda -Ricardas -Ricardina -Ricardo -Ricart -Riccardo -Rich -Richar -Richard -Richart -Richelle -Richie -Rick -Rickey -Ricki -Rickie -Ricky -Rico -Rida -Rider -Ridha -Ridouan -Ridouane -Riduan -Ridwan -Rie -Riera -Rifat -Riffat -Rigel -Rigoberto -Rihab -Riham -Rihanna -Rikardo -Rikki -Riley -Rilma -Rim -Rima -Rimantas -Rimas -Rime -Rimer -Rimma -Rimsha -Rimvydas -Rina -Rinaldo -Rinat -Rino -Rio -Riquelme -Risa -Rishabha -Rishi -Rishikesh -Rita -Ritaj -Rito -Ritu -Ritva -Riva -Rivka -Riyad -Riza -Rizwan -Rizwana -Rkia -Rkiya -Roa -Roald -Roar -Rob -Robbi -Robbie -Robbin -Robby -Robbyn -Robel -Robena -Rober -Robert -Roberta -Robertas -Roberth -Robertina -Roberto -Roberts -Robin -Robina -Robinson -Robledo -Robson -Robt -Robustiana -Robustiano -Robyn -Roc -Roca -Rocco -Rochdi -Rochel -Rochell -Rochelle -Rocio -Rocky -Rod -Rodaina -Rodayna -Rode -Rodel -Roderic -Roderick -Rodger -Rodica -Rodion -Rodney -Rodolf -Rodolfo -Rodolphe -Rodovica -Rodrick -Rodrigo -Rodulfo -Roel -Roelof -Roeya -Rofaida -Rogeli -Rogelia -Rogelio -Roger -Rogerio -Rogers -Rohan -Rohit -Roi -Rokas -Rokaya -Roke -Rokhaya -Rokia -Roksana -Roksolana -Rolan -Roland -Rolanda -Rolandas -Rolande -Rolando -Roldan -Rolf -Rolindes -Rolland -Rolly -Roly -Roma -Romain -Romaine -Romaisa -Romaisaa -Romaisae -Romaissa -Romaissae -Roman -Romana -Romano -Romans -Romarey -Romario -Romas -Romaysa -Romaysaa -Romaysae -Romayssa -Romayssae -Romel -Romelia -Romen -Romeo -Romer -Romero -Romeu -Romica -Romilda -Romina -Rommel -Romona -Romuald -Romualda -Romualdas -Romualdo -Romul -Romulo -Romulus -Romy -Ron -Rona -Ronal -Ronald -Ronaldo -Ronan -Roncesvalles -Ronda -Rong -Rongfen -Rongguang -Ronghua -Rongjun -Rongrong -Rongsheng -Roni -Ronja -Ronna -Ronni -Ronnie -Ronny -Rony -Roosevelt -Roque -Rory -Ros -Rosa -Rosabel -Rosaida -Rosal -Rosalba -Rosalbina -Rosalee -Rosali -Rosalia -Rosalie -Rosalin -Rosalina -Rosalind -Rosalinda -Rosaline -Rosalino -Rosalio -Rosalva -Rosalvina -Rosalyn -Rosamaria -Rosamond -Rosana -Rosane -Rosangel -Rosangela -Rosann -Rosanna -Rosanne -Rosanny -Rosari -Rosaria -Rosario -Rosaura -Rosauro -Roscoe -Rose -Roseane -Roseann -Roseanna -Roseanne -Rosel -Roselee -Roseli -Roselia -Roselin -Roselina -Roseline -Rosell -Rosella -Roselle -Rosely -Roselyn -Roselyne -Rosemarie -Rosemary -Rosemberg -Rosemeire -Rosemery -Rosen -Rosena -Rosend -Rosenda -Rosendo -Rosenilda -Roser -Rosetta -Rosette -Roshan -Roshni -Rosi -Rosia -Rosiane -Rosibel -Rosica -Rosie -Rosilda -Rosilene -Rosimar -Rosimeire -Rosina -Rosinda -Rosineide -Rosio -Rosita -Rositsa -Roslyn -Rosmari -Rosmarie -Rosmary -Rosmen -Rosmeri -Rosmery -Rosmira -Roso -Ross -Rossana -Rossano -Rosse -Rossella -Rossemary -Rossend -Rossie -Rossio -Rossitza -Rossmery -Rossy -Rostislav -Rostyslav -Roswitha -Rosy -Rouchdi -Roudaina -Roumaisa -Roumaissa -Roumaissae -Roumen -Roumiana -Rowena -Roxana -Roxane -Roxann -Roxanna -Roxanne -Roxie -Roxy -Roy -Roya -Royal -Royce -Royer -Royston -Roza -Rozalia -Rozalina -Rozaliya -Rozanne -Rozella -Rozica -Rozina -Rqia -Rquia -Ru -Rubby -Rubel -Ruben -Rubens -Rubi -Rubia -Rubialejos -Rubie -Rubiel -Rubiela -Rubin -Rubina -Ruby -Rubye -Ruddy -Rudesinda -Rudesindo -Rudi -Rudiger -Rudolf -Rudolph -Rudy -Rueben -Ruel -Rufa -Rufina -Rufino -Rufo -Rufus -Ruggero -Ruht -Rui -Ruiman -Ruiying -Rukhsana -Rukhsar -Rumaisa -Rumen -Rumilda -Rumyana -Rumyanka -Rune -Ruoxi -Rupert -Ruperta -Ruperto -Rupinder -Rus -Rusalin -Rusi -Ruska -Ruslan -Ruslana -Ruslanas -Russ -Russel -Russell -Rustam -Rusty -Rusu -Rusudan -Rut -Ruta -Rute -Ruth -Rutha -Ruthann -Ruthanne -Ruthe -Ruthie -Rutilio -Ruxanda -Ruxandra -Ruy -Ruyi -Ruyman -Ruzanna -Ryad -Ryan -Ryann -Rym -Ryszard -Rytis -Saad -Saada -Saadia -Saadiya -Saaid -Saaida -Saba -Sabah -Sabas -Sabastian -Sabela -Saber -Sabiñe -Sabi -Sabiha -Sabin -Sabina -Sabine -Sabiniana -Sabiniano -Sabino -Sabir -Sabira -Sabra -Sabri -Sabria -Sabrin -Sabrina -Sabrine -Sacha -Sachiko -Sacramento -Sacramentos -Sada -Sadaf -Sadako -Sadaqat -Saddik -Sade -Sadek -Sadia -Sadibou -Sadie -Sadik -Sadika -Sadio -Sadiq -Sadou -Sadrac -Sadurni -Sadye -Saed -Saeed -Saeeda -Saeid -Saer -Safa -Safaa -Safae -Safah -Safdar -Safeer -Safi -Safia -Safiatou -Safietou -Safina -Safira -Safiya -Safouan -Safouane -Safta -Saftica -Safuan -Safwan -Sagar -Sagara -Sage -Saghir -Sagrado -Sagrario -Sahagun -Sahar -Sahara -Sahel -Sahida -Sahil -Sahila -Sahira -Sahli -Sahra -Saiba -Saibo -Saibou -Said -Saida -Saidi -Saidia -Saidou -Saif -Saifeddine -Saiful -Saihou -Saikou -Saila -Saily -Saim -Saima -Saimon -Sainey -Sainza -Saioa -Saiqa -Saira -Sajad -Sajadeva -Sajid -Sajida -Sajjad -Sakina -Sakira -Sakura -Sal -Saladina -Saladino -Salah -Salahddine -Salahdin -Salahdine -Salaheddin -Salaheddine -Salahedin -Salam -Salama -Salamata -Salamu -Salas -Salatiel -Salca -Salceda -Salec -Saleck -Saleem -Saleh -Saleha -Salek -Salem -Salema -Salena -Sales -Salesa -Saleta -Salete -Salha -Sali -Salia -Salido -Saliente -Salif -Salifou -Salifu -Salih -Saliha -Salim -Salima -Salimata -Salimatou -Salimou -Salimu -Salina -Saliou -Saliu -Salka -Sallam -Salley -Sallie -Sally -Salma -Salman -Salmane -Saloa -Salobral -Salobrar -Salome -Salomia -Salomon -Saloua -Saloum -Salsabil -Salua -Salud -Saludina -Salustiana -Salustiano -Salut -Salva -Salvacion -Salvador -Salvadora -Salvatore -Salvi -Salvia -Salvina -Salvino -Salvio -Salwa -Saly -Sam -Sama -Samad -Samael -Samah -Samai -Samaira -Saman -Samanda -Samanta -Samantha -Samar -Samara -Samaria -Samarita -Samatha -Samay -Samba -Sambala -Sambor -Sambou -Sameer -Sameh -Sameiro -Samella -Samer -Samera -Sami -Samia -Samih -Samiha -Samina -Samir -Samira -Samiya -Sammie -Sammy -Samoil -Samoila -Samou -Sampedro -Sampson -Samra -Samreen -Samson -Samual -Samuel -Samuele -Samuil -Samvel -Samy -Samya -Samyra -San -Sana -Sanaa -Sanae -Sanah -Sanam -Sancha -Sancho -Sanda -Sandalia -Sandalio -Sandee -Sandeep -Sandel -Sander -Sandi -Sandica -Sandie -Sandita -Sandor -Sandra -Sandrina -Sandrine -Sandro -Sandu -Sandy -Sanela -Sanford -Sang -Sangeeta -Sangita -Sani -Sania -Saniya -Sanja -Sanjay -Sanjaya -Sanjeev -Sanjuana -Sanjuanita -Sankung -Sanna -Sanne -Sanora -Sanoussy -Sanpedro -Sanson -Santa -Santacruz -Santana -Santas -Santi -Santiaga -Santiago -Santina -Santino -Santisima -Santo -Santokh -Santos -Santosh -Sants -Santusa -Sanya -Sanyaia -Saori -Sapna -Saqib -Saqlain -Sara -Sarabel -Sarabi -Sarabjit -Sarah -Sarahi -Sarahy -Sarai -Saraima -Sarama -Saran -Saraniu -Sarasvati -Sarata -Saray -Sarbjit -Sardar -Sare -Sarela -Sarfaraz -Sarfraz -Sargis -Sari -Sarina -Sarita -Sarjo -Sarka -Saroa -Sarr -Sarra -Sarunas -Sarwan -Sarwar -Sasa -Sascha -Sasha -Sashka -Sashko -Sasho -Saskia -Sassi -Satenik -Satiro -Satish -Satnam -Satoko -Satou -Satpal -Saturday -Saturia -Saturio -Saturnina -Saturnino -Saturno -Satwinder -Sau -Sauc -Saul -Saula -Saule -Saulius -Saulo -Saundra -Saura -Sausan -Sava -Savanna -Savannah -Saverio -Saveta -Savina -Savino -Savita -Savu -Sawsan -Sayah -Sayd -Sayda -Sayed -Sayoa -Sayon -Sayonara -Sayra -Sayuri -Scarlet -Scarlett -Scheherazade -Scheherezade -Scherezade -Scot -Scott -Scottie -Scotty -Señor -Sean -Season -Seba -Sebastia -Sebastiaan -Sebastian -Sebastiana -Sebastiano -Sebastiao -Sebastien -Sebrina -Seck -Seckou -Seco -Secundila -Secundina -Secundino -Seda -Seddik -See -Seedy -Seema -Seferina -Seferino -Sefora -Sega -Segimon -Segio -Segisfredo -Segismunda -Segismundo -Segunda -Segundina -Segundino -Segundo -Seham -Sehila -Sehrish -Seida -Seidu -Seikou -Seila -Sekhou -Sekou -Sekouba -Sela -Selam -Selena -Selene -Selenia -Selica -Selim -Selin -Selina -Sellam -Sellamia -Selma -Selmira -Seloua -Selva -Sem -Semen -Semida -Semidan -Semiramis -Sen -Sena -Senador -Senaida -Senda -Sendoa -Sendy -Sene -Seneida -Senen -Senent -Seni -Senia -Senida -Senobia -Senorina -Seny -Seppo -September -Sequero -Serafi -Serafim -Serafin -Serafina -Seraj -Serapia -Serapio -Serban -Serena -Serenella -Serezade -Serge -Sergei -Sergej -Sergejs -Sergejus -Sergey -Serghei -Sergi -Sergia -Sergii -Sergio -Sergiu -Sergiy -Sergo -Serguei -Serguey -Serhii -Serhiy -Serigne -Serina -Serine -Serita -Serkan -Serotina -Serra -Servanda -Servando -Servilia -Serviliana -Serviliano -Servilio -Servio -Servita -Servulo -Serxio -Seryozha -Set -Setefilla -Seth -Setou -Setsuko -Setti -Sevak -Sevastian -Sevastita -Sevda -Sevdalin -Sevdalina -Sever -Severa -Severiana -Severiano -Severin -Severina -Severine -Severino -Severo -Sevgyul -Sevinch -Seydi -Seydina -Seydou -Seye -Seyla -Seymour -Seynabou -Seyni -Sezgin -Sfia -Sghir -Sha -Shaban -Shabana -Shabbir -Shabir -Shabnam -Shad -Shadi -Shadia -Shady -Shae -Shafaqat -Shafiq -Shafique -Shafqat -Shagufta -Shah -Shahab -Shahbaz -Shaheen -Shahid -Shahida -Shahin -Shahnaz -Shahzad -Shahzaib -Shaida -Shaiel -Shaila -Shaima -Shaimaa -Shaina -Shaira -Shaista -Shakeel -Shakia -Shakil -Shakir -Shakira -Shakita -Shakuntala -Shala -Shalanda -Shalini -Shalma -Shalon -Shalonda -Shalva -Shama -Shamaila -Shamara -Shamas -Shameka -Shamika -Shamil -Shamim -Shamir -Shamira -Shamraiz -Shamsa -Shamshad -Shamsher -Shan -Shana -Shanae -Shanda -Shandi -Shandra -Shane -Shaneka -Shanel -Shanell -Shanelle -Shani -Shania -Shanice -Shanika -Shaniqua -Shanita -Shanna -Shannan -Shannon -Shanon -Shanshan -Shanta -Shantae -Shantay -Shante -Shantel -Shantell -Shantelle -Shanti -Shaofen -Shaohua -Shaojun -Shaolin -Shaomei -Shaoping -Shaoqin -Shaowei -Shaoying -Shaoyong -Shaquana -Shaquita -Shara -Sharai -Sharan -Sharay -Sharda -Sharee -Sharell -Sharen -Shari -Sharice -Sharie -Sharif -Sharika -Sharilyn -Sharita -Sharla -Sharleen -Sharlene -Sharmaine -Sharmila -Sharmin -Sharolyn -Sharon -Sharonda -Sharri -Sharron -Sharyl -Sharyn -Shasha -Shasta -Shaukat -Shaun -Shauna -Shaunda -Shaunna -Shaunta -Shaunte -Shavon -Shavonda -Shavonne -Shawana -Shawanda -Shawanna -Shawn -Shawna -Shawnda -Shawnee -Shawnna -Shawnta -Shay -Shayan -Shayla -Shayma -Shayna -Shayne -Shazia -Shea -Sheba -Sheela -Sheena -Shehzad -Sheikh -Sheila -Sheilah -Sheima -Shela -Shelba -Shelby -Sheldon -Shelena -Shelia -Shella -Shelley -Shelli -Shellie -Shelly -Shelton -Shemeka -Shemika -Shena -Sheng -Shenghua -Shengjie -Shengli -Shengwei -Shengyi -Shenika -Shenita -Shenna -Sher -Shera -Sheraz -Sheree -Sherell -Sherezade -Sheri -Sherice -Sheridan -Sherie -Sherif -Sheriff -Sheriffo -Sherika -Sherill -Sherilyn -Sherin -Sherise -Sherita -Sherlene -Sherley -Sherly -Sherlyn -Sherman -Sheron -Sherrell -Sherri -Sherrie -Sherril -Sherrill -Sherron -Sherry -Sherryl -Sherwin -Sherwood -Shery -Sheryl -Sheryll -Sheyla -Shi -Shiela -Shijie -Shila -Shiloh -Shin -Shiqi -Shira -Shirely -Shirin -Shirl -Shirlee -Shirleen -Shirlene -Shirley -Shirly -Shishi -Shiva -Shiyi -Shiyong -Shizue -Shizuko -Shoaib -Shon -Shona -Shonda -Shondra -Shonna -Shonta -Shorena -Shoshana -Shota -Shoukat -Shu -Shuai -Shuang -Shuangfen -Shuangfeng -Shuangjun -Shuangli -Shuangmei -Shuangshuang -Shuangwei -Shuangyan -Shuangyi -Shufang -Shufen -Shuhua -Shuhui -Shujing -Shujuan -Shuli -Shuling -Shumaila -Shumei -Shun -Shuo -Shuping -Shuqin -Shushanik -Shuting -Shuwei -Shuyan -Shuyi -Shuying -Shuzhen -Shyam -Shyla -Si -Sia -Siaka -Siamak -Sian -Siara -Siarhei -Sibel -Sibila -Sibilla -Sibisse -Sibyl -Sibylle -Sica -Sicilia -Sid -Sidahmed -Sidati -Sidaty -Sidi -Sidibe -Sidiki -Sidnei -Sidney -Sidonia -Sidonio -Sidra -Sidy -Siegfried -Sieglinde -Siena -Sienna -Sierra -Sif -Sifeddine -Sigerico -Sigfredo -Sigfrid -Sigfrido -Sigifredo -Sigita -Sigitas -Signe -Sigrid -Sigrun -Sigurd -Siham -Sihame -Sihan -Sihem -Sikandar -Sikander -Sila -Silas -Silda -Silene -Silke -Silly -Silmara -Silva -Silvana -Silvania -Silvano -Silver -Silveri -Silveria -Silverio -Silvestra -Silvestre -Silvestru -Silvia -Silvian -Silviana -Silviano -Silvica -Silvie -Silvija -Silvina -Silvino -Silvio -Silviu -Silviya -Sima -Simao -Simas -Simeon -Simeona -Simi -Simina -Simion -Simo -Simohamed -Simon -Simona -Simone -Simoneta -Simonetta -Simonne -Simplicia -Simplicio -Simran -Simranjit -Simy -Sina -Sinai -Sinaita -Sinan -Sinay -Sinda -Sindia -Sindo -Sindulfo -Sindy -Sinead -Sinesia -Sinesio -Sinforiana -Sinforiano -Sinforosa -Sinforoso -Singh -Sinisa -Sinivali -Sintia -Sinue -Sinuhe -Siny -Sinziana -Siobhan -Siomara -Sion -Siqi -Sira -Siradio -Siraj -Siranush -Sirats -Sire -Sirena -Siri -Siria -Siricio -Siriman -Sirin -Sirine -Sirio -Sirkka -Sirlei -Sirlene -Sirley -Siro -Sisenando -Sisi -Sisinia -Sisinio -Sissel -Sita -Siu -Sixta -Sixte -Sixto -Siyi -Siyka -Siyu -Skaiste -Skye -Slama -Slavcho -Slavi -Slavica -Slavka -Slavko -Slavomir -Slawomir -Sliman -Slimane -Slobodan -Slyvia -Smahan -Smahane -Smail -Smain -Smaine -Smaranda -Smart -Sneha -Snejana -Snezana -Snezha -Snezhana -Snezhanka -Snizhana -Soña -So -Soad -Soadia -Sobeida -Sobia -Sobiha -Socaina -Socorro -Socrates -Soda -Sodia -Sofia -Sofian -Sofiane -Sofica -Sofie -Sofiia -Sofija -Sofio -Sofiya -Sofka -Sofya -Sofyan -Sogues -Sohaib -Sohail -Sohaila -Sohan -Sohayb -Sohayla -Sohel -Sohora -Sohra -Soiartze -Soila -Sokaina -Sokayna -Sokhna -Sol -Solaiman -Solana -Solange -Solangel -Solano -Solanyi -Solayman -Solaymane -Sole -Soledad -Soledat -Solemnidad -Soletat -Soliman -Solita -Solmira -Solo -Solomane -Solomia -Solomiya -Solomon -Solutor -Solveig -Som -Somaya -Somer -Somia -Somiya -Sommer -Somna -Son -Sona -Sonali -Sonam -Sonata -Sondos -Sondra -Song -Sonia -Sonja -Sonnia -Sonny -Sonsoles -Sonya -Soo -Soodia -Sook -Soon -Sophia -Sophie -Sophio -Sopio -Sora -Soraia -Soraida -Soraima -Soralla -Sorangel -Soraya -Sorayda -Soren -Soria -Soriba -Sorin -Sorina -Sorinel -Sorkunde -Sorne -Sory -Sotera -Sotero -Soterraña -Soterraño -Sotirios -Souaad -Souad -Souadia -Soubiha -Soudia -Soufia -Soufian -Soufiane -Soufyan -Soufyane -Souhaib -Souhail -Souhaila -Souhayla -Souhila -Soukaina -Soukayna -Soukeina -Soulaiman -Soulaimane -Soulayman -Soulaymane -Souleye -Souleymane -Souliman -Soulimane -Soultana -Soumaia -Soumaila -Soumana -Soumaya -Soumeya -Soumia -Soumicha -Soumiya -Soumya -Soundous -Soungalo -Sounia -Souraya -Souria -Sow -Sparkle -Spartak -Spas -Spaska -Spencer -Speranta -Spiridon -Spring -Spyridon -Sraddha -Srivatsa -Stacee -Stacey -Staci -Stacia -Stacie -Stacy -Stalin -Stamen -Stan -Stana -Stanca -Stancho -Stanciu -Stancu -Stanel -Stanford -Stanica -Stanimir -Stanimira -Stanislas -Stanislav -Stanislava -Stanislaw -Stanislawa -Stanka -Stanko -Stanley -Stanton -Stanuta -Star -Starla -Starr -Stasia -Stasys -Stavros -Steen -Steeven -Stefan -Stefana -Stefanel -Stefani -Stefania -Stefanica -Stefanie -Stefanita -Stefaniya -Stefanka -Stefanny -Stefano -Stefanov -Stefany -Stefcho -Steffanie -Steffany -Steffen -Steffi -Stefka -Stein -Steinar -Stela -Stelian -Steliana -Stelica -Steliyan -Stella -Stelu -Steluta -Stepan -Stepanie -Stepaniya -Stephaine -Stephan -Stephane -Stephani -Stephania -Stephanie -Stephany -Stephen -Stephenie -Stephine -Stephnie -Stere -Sterica -Sterling -Stevan -Steve -Steven -Stevens -Stevie -Stewart -Sthefany -Stig -Stilian -Stiliyan -Stine -Stivan -Stiven -Stoian -Stoica -Stoil -Stormy -Stoyan -Stoyanka -Stoyanov -Stoyanova -Stoycho -Stoyka -Stoyko -Strahil -Stuart -Su -Sua -Suad -Suanne -Subhadra -Subhan -Sucaina -Success -Suceso -Sudie -Sue -Sueann -Suelen -Sueli -Suellen -Suely -Suevia -Sufen -Sufian -Sufyan -Sugey -Sughran -Sugoi -Suhaib -Suhail -Suhaila -Suhar -Suhong -Suhua -Suifen -Suihong -Suihua -Suiju -Suimei -Suiping -Suiying -Suizhu -Sujing -Sujuan -Suk -Sukaina -Sukayna -Sukeina -Sukhbir -Sukhdev -Sukhjinder -Sukhjit -Sukhvinder -Sukhvir -Sukhwinder -Sulaika -Sulaima -Sulaiman -Sulami -Sulamita -Sulan -Sulay -Sulayman -Suleica -Suleika -Suleima -Suleiman -Suleimane -Sulema -Suleman -Suleyman -Suli -Suliman -Suling -Sullivan -Sulma -Sulpicio -Sultan -Sultana -Sumaia -Sumaila -Sumaira -Suman -Sumaya -Sumei -Sumera -Sumia -Sumiko -Sumisa -Summer -Sumon -Sun -Sunamita -Sunay -Sundas -Sunday -Sundus -Sung -Sunil -Sunilda -Sunita -Suniva -Sunni -Sunny -Sunshine -Suping -Suqin -Suraj -Surama -Suren -Suresh -Suri -Suria -Surinder -Surjeet -Surjit -Surya -Susagna -Susan -Susana -Susane -Susann -Susanna -Susannah -Susanne -Susano -Sushil -Susi -Susie -Suso -Susy -Suwei -Suyan -Suyapa -Suying -Suyog -Suyong -Suyun -Suzan -Suzana -Suzann -Suzanna -Suzanne -Suzette -Suzhen -Suzhu -Suzi -Suzie -Suzy -Svajunas -Svein -Sven -Svenja -Svenska -Sverre -Svetla -Svetlana -Svetlin -Svetlozar -Svetoslav -Svetoslava -Svetozar -Sviatlana -Sviatoslav -Svilen -Svitlana -Svyatoslav -Sybil -Sybille -Syble -Sydney -Syed -Syeda -Sylla -Sylvain -Sylvana -Sylvester -Sylvia -Sylviane -Sylvie -Sylwester -Sylwia -Synnove -Synthia -Syra -Syreeta -Syuleyman -Syuzanna -Szabolcs -Szilard -Szilvia -Szymon -Ta -Taanant -Tabare -Tabata -Tabatha -Tabetha -Tabita -Tabitha -Tache -Taciana -Tacko -Tacoremi -Tad -Tadas -Tadea -Tadeo -Tadeusz -Taha -Tahar -Taher -Tahiche -Tahir -Tahira -Tahirou -Tahis -Tahmina -Tahra -Tai -Taib -Taibi -Taida -Taieb -Taila -Taimoor -Taina -Taira -Tais -Taisa -Taisha -Taisia -Taisiya -Tajamal -Tajinder -Tajuana -Takako -Takashi -Takeshi -Takisha -Talal -Talat -Taleb -Talha -Talia -Talib -Talisha -Talita -Talitha -Talla -Talwinder -Tam -Tama -Tamaanant -Tamala -Tamar -Tamara -Tamari -Tamas -Tamatha -Tamaz -Tamba -Tambra -Tameika -Tameka -Tamekia -Tamela -Tamer -Tamera -Tamesha -Tami -Tamica -Tamie -Tamika -Tamiko -Tamila -Tamimount -Tamimunt -Tamisha -Tammara -Tammera -Tammi -Tammie -Tammy -Tamou -Tamra -Tamsir -Tamta -Tana -Tanase -Tanausu -Tandra -Tandy -Taneka -Tanesha -Tangela -Tania -Tanika -Tanisha -Tanit -Tanja -Tanna -Tanner -Tanta -Tantica -Tanveer -Tanvir -Tanya -Tanyo -Tanzeela -Tanzila -Tao -Taoufik -Taoufiq -Tapha -Tara -Tarah -Taras -Tarcisio -Tareixa -Tarek -Taren -Tareq -Targelia -Tari -Tariel -Tarik -Tariq -Tarlochan -Tarra -Tarsem -Tarsha -Tarsicio -Tarsila -Tarsilo -Tarun -Taryn -Tasawar -Tasha -Tashia -Tashina -Tasia -Tasio -Tasleem -Tasneem -Tasnim -Tassnim -Tata -Tatevik -Tatia -Tatiana -Tatiane -Tatjana -Tatsiana -Tatum -Tatyana -Taufik -Taunya -Tautvydas -Tavita -Tawana -Tawanda -Tawanna -Tawfik -Tawfiq -Tawna -Tawny -Tawnya -Tayeb -Tayisiya -Taylor -Tayna -Tayra -Tayri -Taysa -Taysir -Tayyab -Tayyaba -Tea -Teba -Tecla -Ted -Teddy -Teena -Tegaday -Tegan -Tehmina -Tehmine -Teia -Teimuraz -Teisha -Tejinder -Telesfora -Telesforo -Tello -Telm -Telma -Telmo -Telva -Telvina -Temeka -Temenuzhka -Temika -Temistocles -Tempie -Temple -Temur -Tena -Tenesha -Tenesor -Tenesoya -Tengiz -Tenisha -Tennie -Tennille -Teo -Teobaldo -Teodocia -Teodolina -Teodolinda -Teodomira -Teodomiro -Teodor -Teodora -Teodorico -Teodorino -Teodoro -Teodosi -Teodosia -Teodosio -Teodula -Teodulo -Teofanes -Teofil -Teofila -Teofilo -Teogenes -Teolinda -Teolindo -Teona -Teonila -Teotimo -Teotista -Teotiste -Tequila -Tera -Tere -Tereasa -Terence -Terencia -Terenciano -Terencio -Teres -Teresa -Terese -Teresia -Teresiano -Teresina -Teresita -Tereso -Teressa -Tereza -Terezia -Terezinha -Teri -Terica -Terina -Terisa -Terje -Terra -Terrance -Terrell -Terrence -Terresa -Terri -Terrie -Terrilyn -Terry -Tertuliano -Tesha -Tesifon -Tesifonte -Teslem -Tess -Tessa -Tessie -Tessy -Tetiana -Tetyana -Teudiselo -Teunis -Tewfik -Texenen -Texeneri -Texenery -Tfarah -Tfarrah -Thad -Thaddeus -Thais -Thalia -Thalita -Thamar -Thamara -Thami -Thanh -Thania -Thao -Thaynara -Thays -Thea -Theda -Thelma -Theo -Theodor -Theodora -Theodore -Theodoros -Theodorus -Theola -Theophilus -Theresa -Therese -Theresia -Theressa -Theron -Thersa -Thi -Thiago -Thiam -Thibault -Thibaut -Thierno -Thierry -Thilo -Thomas -Thomasena -Thomasina -Thomasine -Thor -Thora -Thorsten -Thresa -Thu -Thurman -Thuy -Tia -Tiago -Tian -Tiana -Tianhao -Tianna -Tiantian -Tianxiang -Tianyu -Tiara -Tiare -Tiberio -Tiberiu -Tiberius -Tibiabin -Tibisay -Tibor -Tiburcia -Tiburcio -Ticiana -Ticiano -Ticu -Tida -Tidiane -Tieb -Tiemoko -Tien -Tiera -Tierra -Tiesha -Tifa -Tifani -Tifany -Tiffaney -Tiffani -Tiffanie -Tiffany -Tiffiny -Tigist -Tigran -Tihomir -Tijan -Tijana -Tijani -Tijania -Tijuana -Tilda -Till -Tillie -Tilo -Tim -Timea -Timika -Timmy -Timo -Timofei -Timofey -Timotea -Timotei -Timoteo -Timothee -Timothy -Timur -Tina -Tinatin -Tinca -Tincuta -Tindaya -Tinerfe -Ting -Tingting -Tinguaro -Tinisha -Tinixara -Tinka -Tino -Tiny -Tirma -Tirsa -Tirso -Tisa -Tiscar -Tish -Tisha -Tita -Titel -Titi -Titiana -Titina -Tito -Titu -Titus -Tiziana -Tiziano -Tlaitmas -Tlaitmass -Tlaytmas -Tlaytmass -Tleitmas -Toñi -Toader -Tobi -Tobias -Tobie -Toby -Toccara -Tod -Todd -Todor -Todora -Todorka -Todorov -Todorova -Togarma -Tohami -Toi -Tolentina -Tolentino -Tom -Toma -Tomas -Tomasa -Tomasina -Tomasz -Tome -Tomeka -Tomeu -Tomi -Tomika -Tomiko -Tomislav -Tomita -Tommaso -Tommie -Tommy -Tommye -Tomoko -Ton -Tona -Tonatiuh -Toncho -Tonda -Tone -Tonet -Tonette -Toney -Tong -Toni -Tonia -Tonica -Tonie -Tonisha -Tonita -Tonja -Tonka -Tonny -Tono -Tony -Tonya -Toqeer -Tor -Tora -Torahi -Torben -Torbjorn -Torcuata -Torcuato -Tore -Tori -Toria -Toribia -Toribio -Torie -Tornike -Torri -Torrie -Torsten -Tory -Tosha -Toshia -Toshiko -Toshko -Totka -Touba -Toucha -Touda -Toufik -Toufiq -Touhami -Toumani -Toumany -Touraya -Toure -Touria -Touriya -Tourya -Toutou -Tova -Tove -Towanda -Toya -Tracee -Tracey -Traci -Tracie -Tracy -Traian -Tran -Trancito -Trandafir -Trandafira -Trang -Transfiguracion -Transito -Traore -Traute -Travis -Treasa -Treena -Tremedal -Trena -Trent -Trenton -Tresa -Tressa -Tressie -Treva -Trevor -Trey -Triana -Tricia -Trifina -Trifon -Trifonia -Trijntje -Trina -Trine -Trinh -Trini -Trinidad -Trinitario -Trinitat -Trinity -Trino -Trish -Trisha -Trista -Tristan -Tristana -Trond -Troy -Trudi -Trudie -Trudy -Trula -Truman -Tsanko -Tsenka -Tsetsa -Tsetska -Tsira -Tsonka -Tsveta -Tsvetan -Tsvetana -Tsvetanka -Tsvetelin -Tsvetelina -Tsvetko -Tsvetomir -Tsvetomira -Tsvetoslav -Tu -Tuan -Tuastri -Tubal -Tudor -Tudora -Tudorel -Tudorica -Tudorina -Tudorita -Tula -Tulia -Tulio -Tullio -Tunde -Tura -Turia -Turid -Turruchel -Tuyet -Twana -Twanda -Twanna -Twila -Twyla -Txaber -Txaro -Txell -Txema -Txomin -Ty -Tyesha -Tyisha -Tyler -Tynisha -Tyra -Tyree -Tyrell -Tyron -Tyrone -Tyson -Tzvetan -Tzvetanka -Tzvetelina -Uarda -Ubalda -Ubaldina -Ubaldino -Ubaldo -Ubay -Uberney -Udane -Uday -Udo -Ugaitz -Ugne -Ugo -Ugutz -Uhaitz -Ujue -Ul -Ula -Uladzimir -Uldarico -Ulf -Uliana -Ulises -Ulisses -Ulla -Ullah -Ulpiana -Ulpiano -Ulrich -Ulrike -Ulyana -Ulysses -Uma -Umaima -Umair -Umar -Umaro -Umaru -Umayma -Umbelina -Umberto -Umer -Umme -Un -Una -Unai -Unax -Unay -Unni -Upa -Ur -Uraitz -Urania -Uranius -Urano -Urbana -Urbano -Urbelina -Urbez -Urbici -Urcesina -Urcesino -Urdax -Urgell -Uri -Urias -Uriel -Urki -Urko -Urrategui -Urs -Ursel -Ursicina -Ursicinio -Ursicino -Ursicio -Ursina -Ursino -Urso -Ursula -Ursulina -Urszula -Urtza -Urtzi -Urvashi -Usama -Usha -Usman -Usoa -Ussama -Ussumane -Usua -Usue -Usune -Uta -Ute -Utman -Uvaldina -Uwe -Uxia -Uxio -Uxoa -Uxua -Uxue -Uxune -Uxuri -Uyi -Uzair -Uzma -Uzuri -Vachagan -Vaclav -Vaclovas -Vada -Vadim -Vadims -Vadym -Vagner -Vahagn -Vahan -Vahe -Vahram -Vaida -Vaidas -Vaidotas -Vaitiare -Vaiu -Vaiva -Vakhtang -Val -Valarie -Valda -Valdas -Valdeci -Valdecir -Valdefuentes -Valdemar -Valdemir -Valdete -Valdir -Valdirene -Valencia -Valene -Valenti -Valentin -Valentina -Valentinas -Valentine -Valentino -Valentyn -Valentyna -Valer -Valera -Valeri -Valeria -Valerian -Valeriana -Valeriano -Valerica -Valerico -Valerie -Valerii -Valeriia -Valerij -Valerija -Valerijs -Valerijus -Valerio -Valeriu -Valeriy -Valeriya -Valero -Valery -Valeska -Vali -Valia -Valiantsina -Valko -Vall -Valle -Vallie -Vallivana -Valme -Valmir -Valorie -Valquiria -Valrie -Valter -Valvanera -Valvanuz -Valverde -Valya -Van -Vance -Vanda -Vandana -Vanderlei -Vanesa -Vanessa -Vanetta -Vania -Vanilda -Vanina -Vanita -Vanja -Vanna -Vannesa -Vannessa -Vanusa -Vanuza -Vanya -Vanyo -Vardan -Varduhi -Varinder -Varinia -Varsha -Varvara -Vasco -Vashti -Vasil -Vasile -Vasileios -Vasilena -Vasilev -Vasili -Vasilica -Vasiliki -Vasilina -Vasilis -Vasilisa -Vasilka -Vasily -Vaska -Vasko -Vassil -Vasudeva -Vasyl -Vasylyna -Vaughn -Veaceslav -Veda -Veena -Vega -Velda -Velia -Velichka -Velichko -Velika -Velin -Velina -Velislav -Velislava -Velizar -Velko -Vella -Velma -Velva -Velvet -Vena -Venanci -Venancia -Venancio -Vencislav -Venecia -Venelin -Venelina -Venera -Venerable -Venerada -Venerado -Veneranda -Venerando -Venessa -Veneta -Venetta -Veniamin -Venice -Venicia -Venita -Venka -Vennie -Ventsislav -Ventsislava -Ventura -Venus -Veola -Vera -Veracruz -Verania -Verda -Verdell -Verdie -Veredas -Veremundo -Verena -Vergelina -Vergie -Vergil -Vergina -Verginia -Verginica -Verginiya -Veridiana -Veridiano -Verisima -Verisimo -Verka -Verla -Verlene -Verlie -Verline -Vern -Verna -Vernell -Vernetta -Vernia -Vernice -Vernie -Vernita -Vernon -Verona -Veronel -Veronica -Veronika -Veronique -Versie -Vertie -Veruska -Vesela -Veselin -Veselina -Veselka -Veska -Vesko -Vesna -Vesselin -Vesta -Veta -Vetuta -Viñas -Vi -Viacheslav -Viana -Vianca -Vianey -Vianney -Viatcheslav -Vibeke -Vica -Vicenç -Vicencia -Vicencio -Vicens -Vicent -Vicenta -Vicente -Vicentina -Vicentiu -Vicenzo -Vickey -Vicki -Vickie -Vicky -Vico -Victar -Victoire -Victor -Victora -Victoras -Victoria -Victorian -Victoriana -Victoriano -Victorias -Victorico -Victorina -Victorino -Victorio -Victorita -Victoriya -Victory -Vida -Vidal -Vidala -Vidalia -Vidalina -Vidas -Videlina -Vidina -Vidmantas -Viena -Viera -Vieux -Viggo -Vijay -Vikas -Viki -Vikki -Vikram -Viktar -Viktor -Viktoras -Viktoria -Viktoriia -Viktorija -Viktoriya -Viktors -Viktoryia -Vili -Viliam -Vilius -Villa -Villar -Villaviciosa -Vilma -Vilmar -Vilmos -Vilson -Vina -Vince -Vincent -Vincenza -Vincenzo -Vinicio -Vinicius -Vinita -Vinnie -Vinod -Vintila -Vinyet -Viola -Violant -Violante -Violet -Violeta -Violetta -Violette -Violina -Viorel -Viorela -Viorica -Viorika -Vira -Virgelina -Virgen -Virgie -Virgil -Virgili -Virgilia -Virgilijus -Virgilina -Virgilio -Virgiliu -Virgina -Virginia -Virginica -Virginidad -Virginie -Virginija -Virginijus -Virginio -Viriato -Viridiana -Virna -Virtud -Virtudes -Virtuosa -Visan -Vishal -Visita -Visitacion -Visnu -Vita -Vital -Vitali -Vitalia -Vitaliana -Vitaliano -Vitalie -Vitalii -Vitalij -Vitalija -Vitalijs -Vitalijus -Vitalina -Vitalino -Vitaliy -Vitaliya -Vitaly -Vitelio -Vito -Vitor -Vitores -Vitoria -Vitoriana -Vitoriano -Vitorina -Vitorino -Vitorio -Vittoria -Vittorio -Viva -Vivan -Vivas -Vivek -Vivencia -Vivencio -Vivian -Viviana -Viviane -Vivianne -Vivien -Vivienne -Vivina -Vlad -Vlada -Vladas -Vlademir -Vladimer -Vladimir -Vladimira -Vladimiras -Vladimiro -Vladimirs -Vladislao -Vladislav -Vladislava -Vlado -Vladut -Vladyslav -Vladyslava -Vlasta -Voica -Voichita -Voicu -Vojtech -Volha -Volker -Volodya -Volodymir -Volodymyr -Volusiano -Von -Voncile -Vonda -Vonnie -Voro -Vsevolod -Vyacheslav -Vyara -Vytautas -Waclaw -Wade -Wadie -Wadii -Wael -Wafa -Wafaa -Wafae -Wagner -Wahab -Wahba -Waheed -Wahib -Wahiba -Wahid -Wahida -Wai -Wail -Wajid -Walaa -Walae -Walberto -Walburga -Walda -Waldemar -Waldina -Waldino -Waldir -Waldo -Waled -Waleed -Waleska -Walfrido -Walid -Walker -Walkiria -Wallace -Wally -Walquiria -Walter -Walther -Walton -Waltraud -Waltraut -Waly -Wan -Wanda -Wander -Wanderley -Wanderson -Wandifa -Wanessa -Waneta -Wanetta -Wang -Wangfen -Wangjun -Wanita -Wanli -Waqar -Waqas -Ward -Warda -Waris -Warner -Warren -Waseem -Washington -Wasif -Wasila -Wasim -Wasima -Wassila -Wassim -Wassima -Wava -Waylon -Wayne -Weam -Wedad -Wei -Weibin -Weidong -Weifang -Weifen -Weifeng -Weiguang -Weiguo -Weihao -Weihong -Weihua -Weijian -Weijie -Weijing -Weijun -Weili -Weiliang -Weiling -Weimei -Weimin -Weiming -Weiping -Weiqiang -Weiqin -Weirong -Weiwei -Weixiao -Weiyan -Weiyi -Weiying -Weiyong -Weizhen -Weizhong -Weldon -Welington -Wellington -Wen -Wenbin -Wenbo -Wencesla -Wenceslaa -Wenceslada -Wenceslao -Wenche -Wendell -Wendi -Wendie -Wendolyn -Wendy -Wenguang -Wenhao -Wenhua -Wenhui -Wenjian -Wenjie -Wenjing -Wenjuan -Wenjun -Wenli -Wenliang -Wenlong -Wenming -Wenona -Wenqiang -Wensheng -Wentao -Wenting -Wenwei -Wenwen -Wenwu -Wenxiang -Wenxin -Wenyan -Wenyi -Wenying -Wenyuan -Wenzhong -Werner -Weronika -Wes -Wesal -Wesley -Wessal -Weston -Whitley -Whitney -Wiaam -Wiam -Wiame -Widad -Wiebke -Wieslaw -Wieslawa -Wifredo -Wigberto -Wiham -Wijdan -Wijdane -Wiktor -Wiktoria -Wilber -Wilbert -Wilberto -Wilbur -Wilburn -Wilda -Wilder -Wiley -Wilford -Wilfred -Wilfredo -Wilfrida -Wilfrido -Wilfried -Wilhelm -Wilhelmina -Wilhelmine -Wilhelmus -Wilhemina -Wiliam -Wilian -Wilkin -Will -Willa -Willam -Willan -Willard -Willem -Willemina -Willena -Willene -Willetta -Willette -Willi -Willia -William -Williams -Willian -Willians -Willibald -Willie -Williemae -Willington -Willis -Willma -Willodean -Willow -Willy -Wilma -Wilman -Wilmar -Wilmer -Wilson -Wilton -Wilver -Wilzon -Wim -Windy -Winford -Winfred -Winfried -Winifred -Winnie -Winnifred -Winona -Winston -Winter -Wioleta -Wioletta -Wisal -Wisam -Wisdom -Wissal -Wissam -Witold -Wladimir -Wladimiro -Wladyslaw -Wlodzimierz -Wm -Wojciech -Wolf -Wolfgang -Wolfram -Wonda -Woodrow -Wouter -Wu -Wyatt -Wynell -Wynona -X -Xabat -Xabel -Xabi -Xabier -Xabin -Xacobe -Xacobo -Xaime -Xaira -Xairo -Xalo -Xan -Xana -Xandra -Xandre -Xanet -Xantal -Xanti -Xaqueline -Xaquin -Xaver -Xavi -Xavier -Xeber -Xeila -Xela -Xelo -Xena -Xenia -Xenxo -Xerach -Xerman -Xesca -Xesus -Xevi -Xi -Xia -Xian -Xiana -Xianbin -Xianfen -Xianfeng -Xiang -Xiangdong -Xiangmei -Xianguang -Xiangyang -Xiangyu -Xianjun -Xianli -Xianmei -Xianmin -Xianping -Xianwei -Xianyong -Xianzhong -Xiao -Xiaobin -Xiaobing -Xiaobo -Xiaochun -Xiaodan -Xiaodong -Xiaoe -Xiaofang -Xiaofei -Xiaofen -Xiaofeng -Xiaoguang -Xiaohai -Xiaohong -Xiaohua -Xiaohui -Xiaojian -Xiaojie -Xiaojing -Xiaoju -Xiaojuan -Xiaojun -Xiaokang -Xiaolan -Xiaole -Xiaolei -Xiaoli -Xiaolian -Xiaoliang -Xiaolin -Xiaoling -Xiaolong -Xiaomei -Xiaomiao -Xiaomin -Xiaoming -Xiaopeng -Xiaoping -Xiaoqiang -Xiaoqin -Xiaoqing -Xiaoqiong -Xiaoqiu -Xiaoqun -Xiaorong -Xiaoshuang -Xiaowei -Xiaowen -Xiaowu -Xiaoxia -Xiaoxiao -Xiaoxiong -Xiaoxue -Xiaoya -Xiaoyan -Xiaoyang -Xiaoyi -Xiaoying -Xiaoyong -Xiaoyu -Xiaoyue -Xiaoyun -Xiaozhen -Xiaozhong -Xiaozhu -Xiara -Xicotencatl -Xiker -Xim -Ximei -Ximena -Ximo -Xin -Xinbo -Xinfeng -Xing -Xingyu -Xinhao -Xinhua -Xinhui -Xinjian -Xinjie -Xinlei -Xinli -Xinmei -Xinmiao -Xinping -Xinrong -Xinru -Xinrui -Xinwei -Xinxin -Xinya -Xinyan -Xinyao -Xinyi -Xinying -Xinyu -Xinyuan -Xinyue -Xiomara -Xiong -Xira -Xisca -Xisco -Xisela -Xiu -Xiue -Xiufang -Xiufen -Xiufeng -Xiuhong -Xiuhua -Xiujuan -Xiulan -Xiuli -Xiulian -Xiuling -Xiumei -Xiuping -Xiuqin -Xiurong -Xiuwei -Xiuyan -Xiuying -Xiuyu -Xiuyun -Xiuzhen -Xiuzhu -Xixi -Xiya -Xoan -Xoana -Xoaquin -Xochitl -Xoel -Xose -Xu -Xuan -Xuban -Xudong -Xue -Xuebin -Xuefang -Xuefen -Xuefeng -Xuehong -Xuehua -Xuejiao -Xuejing -Xueli -Xueliang -Xueling -Xuemei -Xueping -Xueqin -Xuewei -Xueyan -Xueying -Xueyong -Xuezhen -Xufen -Xufeng -Xuhar -Xuhong -Xujun -Xuli -Xulia -Xulian -Xulio -Xumei -Xumiao -Xun -Xuping -Xurde -Xurxo -Xuwei -Xuyan -Xuyi -Xuying -Xuyong -Xuzhen -Ya -Yaagoub -Yaakoub -Yacin -Yacine -Yackeline -Yaco -Yacoub -Yacouba -Yacqueline -Yadhira -Yadiel -Yadira -Yaeko -Yael -Yago -Yagoba -Yahaira -Yahaya -Yahdih -Yahel -Yahia -Yahima -Yahir -Yahiza -Yahui -Yahya -Yailen -Yailin -Yaima -Yaimara -Yair -Yaira -Yaisa -Yaiza -Yajaira -Yajie -Yajing -Yajnu -Yakelin -Yakeline -Yakhya -Yakira -Yakoub -Yakout -Yakuba -Yakubu -Yalal -Yali -Yalila -Yalile -Yama -Yamadou -Yamal -Yamandu -Yamani -Yamara -Yamel -Yamil -Yamila -Yamile -Yamilei -Yamilet -Yamileth -Yamilex -Yamiley -Yamilka -Yamin -Yamina -Yamira -Yamirka -Yamna -Yan -Yana -Yanai -Yanameyaia -Yanara -Yanay -Yancho -Yancouba -Yandira -Yane -Yaneisy -Yanela -Yaneli -Yanelis -Yanely -Yanelys -Yaneris -Yanet -Yaneth -Yanetsy -Yanett -Yanfang -Yanfen -Yanfeng -Yang -Yangyang -Yanhong -Yanhua -Yani -Yania -Yaniel -Yanik -Yanin -Yanina -Yanine -Yanira -Yanire -Yaniris -Yanis -Yanitsa -Yanitza -Yanjun -Yanka -Yankhoba -Yanko -Yankuba -Yanli -Yanling -Yanmei -Yann -Yanna -Yannick -Yannik -Yannis -Yanping -Yanqin -Yanqing -Yanyan -Yanying -Yanyu -Yanyun -Yanzhen -Yao -Yaoyao -Yapci -Yaping -Yaqi -Yaqin -Yaquelin -Yaqueline -Yara -Yared -Yarel -Yareli -Yarelis -Yarely -Yaret -Yaretzi -Yarey -Yari -Yarima -Yarina -Yarisa -Yarissa -Yaritsa -Yaritza -Yarixa -Yariza -Yaromir -Yaron -Yaroslav -Yaroslava -Yaru -Yaryna -Yasar -Yaseen -Yasemin -Yasen -Yaser -Yash -Yashi -Yashira -Yashmina -Yasiel -Yasin -Yasine -Yasir -Yasira -Yasmani -Yasmany -Yasmeen -Yasmin -Yasmina -Yasmine -Yasmira -Yasna -Yassa -Yassen -Yasser -Yassim -Yassin -Yassine -Yassir -Yassira -Yassmin -Yassmina -Yassmine -Yasu -Yasuko -Yasunari -Yasyn -Yating -Yattou -Yauci -Yaumara -Yave -Yavor -Yaw -Yawad -Yawen -Yaxin -Yaxuan -Yaya -Yazara -Yazid -Yazira -Yazmin -Yazmina -Yazza -Ye -Yecenia -Yedey -Yedir -Yedra -Yee -Yeferson -Yegor -Yeiko -Yeimi -Yeimy -Yeison -Yeiza -Yekaterina -Yelco -Yelena -Yelina -Yelitza -Yelizaveta -Yelko -Yelyzaveta -Yemina -Yen -Yenai -Yenay -Yeneba -Yenedey -Yeneva -Yeney -Yeni -Yenia -Yenifer -Yeniffer -Yenisey -Yenni -Yennifer -Yenny -Yeny -Yer -Yera -Yerai -Yeraldin -Yeray -Yered -Yeremay -Yeremi -Yeremy -Yerena -Yeriel -Yerik -Yerina -Yerko -Yerma -Yero -Yerobe -Yeron -Yerover -Yerson -Yesenia -Yeshika -Yesica -Yesid -Yesika -Yesmin -Yesmina -Yessenia -Yessica -Yessika -Yetta -Yetto -Yeudiel -Yevdokiya -Yevette -Yevgen -Yevgeniy -Yevgeniya -Yevhen -Yevheniy -Yevheniya -Ygnacio -Yguanira -Yi -Yibin -Yibo -Yichen -Yicheng -Yifan -Yifei -Yifeng -Yihad -Yihan -Yihao -Yihua -Yihui -Yijie -Yijing -Yijun -Yikai -Yile -Yilena -Yili -Yilian -Yilin -Yiling -Yilong -Yimei -Yimin -Yiming -Yimmy -Yin -Yina -Yinet -Yineth -Ying -Yingjie -Yingmei -Yingying -Yinhua -Yinuo -Yiping -Yiqiang -Yiqun -Yiran -Yiru -Yisel -Yisela -Yisenia -Yissel -Yiting -Yiwei -Yiwen -Yixiang -Yixin -Yixuan -Yiyi -Yiying -Yizhong -Ylda -Ylena -Ylenia -Yluminada -Ynes -Yngrid -Ynocencia -Yoan -Yoana -Yoandra -Yoania -Yoanka -Yoann -Yoanna -Yobana -Yobany -Yocasta -Yocelin -Yoel -Yogesh -Yohan -Yohana -Yohandra -Yohanka -Yohanna -Yohanny -Yohara -Yokasta -Yokin -Yoko -Yola -Yolanda -Yolande -Yolando -Yolima -Yolimar -Yollotl -Yolonda -Yomaira -Yomara -Yon -Yonai -Yonaida -Yonas -Yonatan -Yonathan -Yonay -Yone -Yones -Yong -Yongbin -Yongfen -Yongfeng -Yongfu -Yongguang -Yonghai -Yonghong -Yonghua -Yongjian -Yongjie -Yongjun -Yongkang -Yongli -Yongliang -Yongmei -Yongmin -Yongming -Yongping -Yongqiang -Yongqin -Yongqing -Yongsheng -Yongwei -Yongxin -Yongyi -Yongzhen -Yoni -Yonka -Yonny -Yony -Yordan -Yordana -Yordanka -Yordanov -Yordany -Yordi -Yordy -Yorel -Yorlady -Yoro -Yoseba -Yosef -Yoselin -Yoselyn -Yoshie -Yoshiko -Yoshua -Yosif -Yosra -Yossef -Yossra -Yosu -Yosua -Yosue -Yosune -Yosvani -Yosvany -Yosyp -Yotuel -Youba -Youcef -Youlanda -Younas -Younes -Youness -Younesse -Young -Younis -Younnes -Younouss -Younoussa -Yousaf -Yousef -Youseff -Yousif -Yousra -Yousri -Youssaf -Youssef -Youssif -Youssou -Youssouf -Youssouph -Youssoupha -Youssra -Yousuf -Yovana -Yovani -Yovanna -Yovanny -Yovany -Yovka -Yrama -Yraya -Yrene -Yria -Yris -Yrma -Ysabel -Ysaura -Ysidora -Ysidro -Ysmael -Ysolina -Yu -Yuan -Yuanyuan -Yubin -Yuchen -Yucheng -Yudelka -Yudelkis -Yuderka -Yudi -Yudit -Yudith -Yudy -Yue -Yuee -Yuefen -Yuehong -Yuehua -Yuemei -Yueping -Yueqin -Yuette -Yueying -Yufang -Yufei -Yufen -Yufeng -Yugo -Yuhan -Yuhang -Yuhao -Yuhong -Yuhua -Yuhui -Yujie -Yujuan -Yujun -Yuk -Yuka -Yukari -Yuki -Yukiko -Yuko -Yuksel -Yulan -Yulanda -Yuleidy -Yuleima -Yuleisy -Yulema -Yulen -Yuli -Yulia -Yulian -Yuliana -Yulianna -Yuliet -Yulieth -Yuliia -Yulimar -Yulin -Yuling -Yulisa -Yulissa -Yuliya -Yuliyan -Yuliyana -Yulong -Yuly -Yuma -Yumalai -Yumalay -Yumara -Yumei -Yumi -Yumiko -Yumin -Yuming -Yun -Yuna -Yunaida -Yunaisy -Yunan -Yune -Yuneida -Yuneisi -Yuneisy -Yunes -Yunfeng -Yung -Yunhua -Yunia -Yuniel -Yunier -Yunior -Yunjie -Yunlong -Yunmei -Yunus -Yunyan -Yunying -Yunyun -Yuonne -Yuping -Yuqi -Yuqin -Yuqing -Yuraima -Yurani -Yurany -Yureima -Yurema -Yurena -Yuri -Yuridia -Yurii -Yurik -Yuriko -Yurima -Yurina -Yuriy -Yurong -Yury -Yusara -Yusef -Yusein -Yusif -Yusimi -Yusimy -Yusleidy -Yusra -Yusraa -Yussef -Yusuf -Yusupha -Yuting -Yutong -Yuval -Yuvraj -Yuwei -Yuxi -Yuxia -Yuxiang -Yuxin -Yuxuan -Yuyan -Yuying -Yuzhen -Yuzhu -Yvan -Yvelisse -Yves -Yvette -Yvon -Yvone -Yvonne -Yzan -Zabulon -Zacaria -Zacarias -Zachariah -Zachary -Zachery -Zack -Zackary -Zada -Zafar -Zafer -Zafira -Zahara -Zahari -Zaharia -Zaharie -Zaheer -Zaher -Zahia -Zahid -Zahida -Zahir -Zahira -Zahoor -Zahra -Zahraa -Zahrae -Zahya -Zaid -Zaida -Zaila -Zaim -Zaima -Zain -Zaina -Zainab -Zaineb -Zaira -Zak -Zaka -Zakari -Zakaria -Zakariaa -Zakariae -Zakarias -Zakariya -Zakariyae -Zakarya -Zaki -Zakia -Zakir -Zakiya -Zalina -Zaloa -Zaman -Zamara -Zambra -Zamfir -Zamfira -Zamir -Zamira -Zan -Zana -Zandra -Zane -Zaneta -Zanib -Zanobi -Zaqueo -Zara -Zarah -Zarai -Zaraida -Zaray -Zarek -Zarina -Zarko -Zaruhi -Zarza -Zaur -Zayd -Zayda -Zaynab -Zayneb -Zayra -Zaza -Zbigniew -Zdenek -Zdenka -Zdravka -Zdravko -Zdzislaw -Ze -Zebensui -Zebensuy -Zebenzui -Zebenzuy -Zeenat -Zeeshan -Zehra -Zeiane -Zeida -Zeina -Zeinab -Zeinabou -Zeineb -Zeinebou -Zelai -Zelda -Zelia -Zeljko -Zella -Zelma -Zeltia -Zena -Zenab -Zenaida -Zenaide -Zeneb -Zeneida -Zeneyda -Zenia -Zenobia -Zenobio -Zenon -Zenona -Zenovia -Zenoviy -Zeshan -Zetta -Zeus -Zeynep -Zhaira -Zhan -Zhana -Zhaneta -Zhang -Zhanna -Zhao -Zhara -Zhe -Zhelyazko -Zhen -Zheng -Zhengwei -Zhengyong -Zhenhua -Zhenni -Zhenya -Zhenyu -Zhenzhen -Zhi -Zhibin -Zhichao -Zhicheng -Zhifeng -Zhigang -Zhiguang -Zhiguo -Zhihao -Zhihong -Zhihua -Zhihui -Zhijian -Zhijie -Zhijun -Zhiliang -Zhilin -Zhimin -Zhiming -Zhipeng -Zhiping -Zhiqiang -Zhirong -Zhisheng -Zhivka -Zhivko -Zhiwei -Zhiwen -Zhixiang -Zhixin -Zhixiong -Zhiyi -Zhiying -Zhiyong -Zhiyuan -Zhong -Zhonghai -Zhonghua -Zhongmin -Zhongping -Zhongwei -Zhongyi -Zhor -Zhora -Zhou -Zhour -Zhu -Zhulieta -Zhuying -Zi -Zia -Ziad -Ziara -Zidane -Zigor -Zihan -Zihao -Zihara -Ziheng -Zijian -Zilda -Zilia -Zilvinas -Zina -Zinab -Zinaida -Zinayida -Zinba -Zine -Zineb -Zineddine -Zinedine -Zinica -Zinnia -Zinoviy -Zinoviya -Zintia -Zion -Ziortza -Ziqi -Zita -Zitouni -Zitounia -Zivile -Zixin -Zixuan -Ziyad -Ziyan -Ziyi -Zlata -Zlatan -Zlatina -Zlatka -Zlatko -Zoa -Zobia -Zobida -Zocueca -Zoe -Zoel -Zofia -Zohaib -Zohair -Zohartze -Zoheir -Zoher -Zohir -Zohra -Zoi -Zoia -Zoica -Zoila -Zoilo -Zoita -Zola -Zolikha -Zoltan -Zona -Zonia -Zora -Zoraida -Zoraima -Zoran -Zoraya -Zorayda -Zoriana -Zorica -Zorion -Zorione -Zorita -Zorka -Zornitsa -Zoryana -Zosima -Zosimo -Zoubaida -Zoubair -Zoubida -Zoubir -Zouhair -Zouheir -Zouhir -Zouhra -Zoulikha -Zoumana -Zoya -Zsofia -Zsolt -Zsuzsa -Zsuzsanna -Zubaida -Zubair -Zuberoa -Zugaitz -Zuhair -Zuhaitz -Zula -Zulaica -Zulaika -Zulaima -Zulay -Zuleica -Zuleika -Zuleima -Zuleja -Zulema -Zuleyka -Zuleyma -Zulfiqar -Zulima -Zully -Zulma -Zulmira -Zulqarnain -Zunaira -Zunbeltz -Zunilda -Zurab -Zurabi -Zuriñe -Zuria -Zurine -Zutoia -Zuzana -Zuzanna -Zviad -Zviadi -Zydrunas -Zygmunt \ No newline at end of file diff --git a/datasets/key b/datasets/key deleted file mode 100644 index 5668d328..00000000 --- a/datasets/key +++ /dev/null @@ -1 +0,0 @@ -/$#i ɛy \ No newline at end of file diff --git a/datasets/label b/datasets/label deleted file mode 100644 index 6e14e8d1..00000000 --- a/datasets/label +++ /dev/null @@ -1 +0,0 @@ -ˍ%v It<0;s4&E#jM \ No newline at end of file diff --git a/datasets/serialized_index b/datasets/serialized_index deleted file mode 100644 index f98d770a89d980b8b5a5da5b28b87ff19bbf8c33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1142017 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