diff --git a/hacspec-scrambledb/scrambledb/Cargo.toml b/hacspec-scrambledb/scrambledb/Cargo.toml index f743442..670069f 100644 --- a/hacspec-scrambledb/scrambledb/Cargo.toml +++ b/hacspec-scrambledb/scrambledb/Cargo.toml @@ -21,7 +21,7 @@ rand = { version = "0.8.5", optional = true } getrandom = { version = "0.2.10", features = ["js"], optional = true } hex = { version = "0.4.3", optional = true } -libcrux = { git = "https://github.com/cryspen/libcrux.git" } +libcrux = { git = "https://github.com/cryspen/libcrux.git", rev = "8889c70b1faf26d131f14442f54a5938ab1deff6" } gloo-utils = { version = "0.1", features = ["serde"] } serde_json = "1.0.106" diff --git a/hacspec-scrambledb/scrambledb/src/data_transformations.rs b/hacspec-scrambledb/scrambledb/src/data_transformations.rs index fe6f568..5896e31 100644 --- a/hacspec-scrambledb/scrambledb/src/data_transformations.rs +++ b/hacspec-scrambledb/scrambledb/src/data_transformations.rs @@ -83,7 +83,7 @@ pub fn blind_pseudonymized_datum( // Blind recovered raw pseudonym towards receiver. let blinded_handle = BlindedPseudonymizedHandle(prepare_blind_convert( *bpk, - store_context.recover_raw_pseudonym(datum.handle.0)?, + store_context.recover_raw_pseudonym(datum.handle)?, randomness, )?); @@ -204,7 +204,7 @@ pub fn finalize_blinded_datum( datum: &BlindedPseudonymizedData, ) -> Result { // Finalize pseudonym for storage. - let handle = FinalizedPseudonym(store_context.finalize_pseudonym(datum.blinded_handle.0)?); + let handle = store_context.finalize_pseudonym(datum.blinded_handle)?; // Decrypt data value for storage. let data_value = hpke_open_level_2(&datum.encrypted_data_value, &store_context.hpke_sk)?; diff --git a/hacspec-scrambledb/scrambledb/src/data_types.rs b/hacspec-scrambledb/scrambledb/src/data_types.rs index f3089ad..9446829 100644 --- a/hacspec-scrambledb/scrambledb/src/data_types.rs +++ b/hacspec-scrambledb/scrambledb/src/data_types.rs @@ -10,13 +10,18 @@ use oprf::coprf::coprf_online::{BlindInput, BlindOutput}; /// A type for finalized pseudonyms, i.e. those which have been hardened for /// storage by applying a PRP. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(test, derive(Hash))] pub struct FinalizedPseudonym(pub(crate) [u8; 64]); /// A type for blinded identifiable handles. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BlindedIdentifiableHandle(pub(crate) BlindInput); /// A type for blinded pseudonymous handles. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct BlindedPseudonymizedHandle(pub(crate) BlindOutput); /// A plain text data value. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct DataValue { /// A byte string encoding the data value. pub(crate) value: Vec, @@ -24,6 +29,7 @@ pub struct DataValue { pub(crate) attribute_name: String, } /// An encrypted data value. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct EncryptedDataValue { /// A byte string encoding the encrypted data value. pub(crate) value: Vec, @@ -34,14 +40,22 @@ pub struct EncryptedDataValue { } /// An identifiable piece of data. +/// +/// `PartialOrd` derive: +/// When derived on structs, it will produce a lexicographic ordering based on +/// the top-to-bottom declaration order of the struct’s members. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct IdentifiableData { /// A plain text handle. + /// Because `PartialOrd` is derived, the order for this struct is + /// lexicographical on this handle. pub(crate) handle: String, /// A plain text data value. pub(crate) data_value: DataValue, } /// The blinded version of an identifiable piece of data. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BlindedIdentifiableData { /// A blinded plain text handle. pub(crate) blinded_handle: BlindedIdentifiableHandle, @@ -50,6 +64,7 @@ pub struct BlindedIdentifiableData { } /// The blinded version of a pseudonymized piece of data. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BlindedPseudonymizedData { /// A blinded pseudonymous handle. pub(crate) blinded_handle: BlindedPseudonymizedHandle, @@ -58,6 +73,7 @@ pub struct BlindedPseudonymizedData { } /// A pseudonymized piece of data. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PseudonymizedData { /// A pseudonymous handle. pub(crate) handle: FinalizedPseudonym, diff --git a/hacspec-scrambledb/scrambledb/src/finalize.rs b/hacspec-scrambledb/scrambledb/src/finalize.rs index 23940b1..14cd6c7 100644 --- a/hacspec-scrambledb/scrambledb/src/finalize.rs +++ b/hacspec-scrambledb/scrambledb/src/finalize.rs @@ -1,60 +1,37 @@ -//! # Conversion Finalization - use crate::{ data_transformations::finalize_blinded_datum, - data_types::{BlindedPseudonymizedData, BlindedPseudonymizedHandle, EncryptedDataValue}, + data_types::{BlindedPseudonymizedData, PseudonymizedData}, error::Error, setup::StoreContext, - table::{Column, ConvertedTable, PseudonymizedTable}, + table::Table, }; -/// The result of a split or join conversion is a set of blinded -/// pseudonymized tables which have been encrypted towards a data store. +/// ## Finalization of Pseudonymous and Converted Tables +/// +/// Finalization of pseudonyms is the same regardless of pseudonym type, +/// i.e. whether they are long term storage pseudonyms at the Data Lake or +/// join pseudonyms at a Data Processor. /// -/// For permanent storage of the pseudonymized data, the raw pseudonyms have -/// to be unblinded and subsequently hardened into permanent pseudonyms. +/// Finalize a table of blinded pseudonymized data values by applying the +/// finalization operation on each entry and shuffling the result: /// -/// In addition the encrypted values need to be decrypted to be available -/// for future conversions towards other data stores. -pub fn finalize_conversion( +/// Inputs: +/// - `store_context`: The data store's pseudonymization context +/// - `table`: A table of blinded pseudonymized data values +/// +/// Output: +/// A table of pseudonymized data values. +pub fn finalize_blinded_table( store_context: &StoreContext, - converted_tables: Vec, -) -> Result, Error> { - let mut pseudonymized_tables = Vec::new(); - - for blinded_table in converted_tables { - let mut pseudonymized_column_data = Vec::new(); - - for (blinded_pseudonym, encrypted_value) in blinded_table.column().data() { - let blinded_pseudonymized_datum = BlindedPseudonymizedData { - blinded_handle: BlindedPseudonymizedHandle(blinded_pseudonym), - encrypted_data_value: EncryptedDataValue { - attribute_name: blinded_table.column().attribute(), - value: encrypted_value, - encryption_level: 2u8, - }, - }; - - let pseudonymized_datum = - finalize_blinded_datum(&store_context, &blinded_pseudonymized_datum)?; - - pseudonymized_column_data.push(( - pseudonymized_datum.handle.0, - pseudonymized_datum.data_value.value, - )); - } - - let mut pseudonymized_column = Column::new( - blinded_table.column().attribute(), - pseudonymized_column_data, - ); - pseudonymized_column.sort(); + table: Table, +) -> Result, Error> { + let mut pseudonymized_data = table + .data() + .iter() + .map(|entry| finalize_blinded_datum(store_context, entry)) + .collect::, Error>>()?; - pseudonymized_tables.push(PseudonymizedTable::new( - blinded_table.identifier(), - pseudonymized_column, - )) - } + pseudonymized_data.sort(); - Ok(pseudonymized_tables) + Ok(Table::new(table.identifier().into(), pseudonymized_data)) } diff --git a/hacspec-scrambledb/scrambledb/src/join.rs b/hacspec-scrambledb/scrambledb/src/join.rs index 31c107b..c5b2103 100644 --- a/hacspec-scrambledb/scrambledb/src/join.rs +++ b/hacspec-scrambledb/scrambledb/src/join.rs @@ -1,107 +1,51 @@ -//! ## Join Conversion +//! # Pseudonym Conversion use hacspec_lib::Randomness; use libcrux::hpke::HpkePublicKey; use oprf::coprf::coprf_setup::BlindingPublicKey; use crate::{ data_transformations::{blind_pseudonymized_datum, convert_blinded_datum}, - data_types::{ - BlindedPseudonymizedData, BlindedPseudonymizedHandle, DataValue, EncryptedDataValue, - FinalizedPseudonym, PseudonymizedData, - }, + data_types::{BlindedPseudonymizedData, PseudonymizedData}, error::Error, setup::{ConverterContext, StoreContext}, - table::{BlindTable, Column, ConvertedTable, PseudonymizedTable}, + table::Table, SECPAR_BYTES, }; -/// ### Preparation -/// In order to process a join request on a number of pseudonymized -/// columns, they are prepared as follows: -/// - To allow for conversion to the join pseudonym, the unblinded coPRF -/// outputs are first retrieved from the lake pseudonyms by applying the -/// inverse of the PRP used when the data was imported to the lake. -/// - Afterwards a blinding of these coPRF outputs is performed towards -/// the data processor as receiver. -/// - In addition the table values are encrypted towards the data -/// processor. +/// ## Blinding Pseudonymous Tables /// -/// ``` text -/// Inputs: -/// context: StoreContext -/// bpk_target: coPRF.BlindingPublicKey -/// ek_target: RPKE.EncryptionKey -/// pseudonymized_tables: List of PseudonymizedTables -/// randomness: uniformly random bytes -/// -/// Output: -/// blinded_tables: List of BlindedTables -/// -/// fn prepare_join_conversion(context, -/// bpk_target, -/// ek_target, -/// pseudonymized_tables, -/// randomness): -/// let blind_tables = Vec::new(); -/// for table in pseudonymized_tables { -/// let blind_column = BlindColumn::new(table.column.attribute()); -/// -/// for (pseudonym, value) in table.column() { -/// let raw_pseudonym = recover_raw_pseudonym(context, pseudonym); -/// let blinded_pseudonym = prepare_blind_convert(bpk_target, raw_pseudonym, randomness); +/// Prepare a table of pseudonymous data values for join conversion by applying +/// the blinding operation on each entry and shuffling the result. /// -/// let encrypted_value = RPKE.encrypt(ek_target, value, randomness); -/// -/// blind_column.push((blinded_pseudonym, encrypted_value)); -/// } -/// blind_column.sort(); +/// Inputs: +/// - `store_context`: The data store's pseudonymization context +/// - `ek_receiver`: The receiver's public encryption key +/// - `bpk_receiver`: The receiver's public blinding key +/// - `pseudonymized_table`: A table of pseudonymous data values +/// - `randomness`: Random bytes /// -/// let blind_table = BlindTable::new(table.identifier(), vec![blind_column]); -/// blind_tables.push(blind_table) -/// } -/// return blind_tables -/// ``` -pub fn prepare_join_conversion( +/// Outputs: +/// A table of blinded pseudonymous data values. +pub fn blind_pseudonymous_table( store_context: &StoreContext, bpk_receiver: BlindingPublicKey, ek_receiver: &HpkePublicKey, - pseudonymized_tables: Vec, + pseudonymized_table: Table, randomness: &mut Randomness, -) -> Result, Error> { - let mut blind_columns = Vec::new(); - - for table in pseudonymized_tables.iter() { - let mut blind_column_data = Vec::new(); - - for (pseudonym, value) in table.column().data() { - let pseudonymized_datum = PseudonymizedData { - handle: FinalizedPseudonym(pseudonym), - data_value: DataValue { - attribute_name: table.column().attribute(), - value: value, - }, - }; - let blinded_pseudonymized_datum = blind_pseudonymized_datum( - &store_context, - &bpk_receiver, - &ek_receiver, - &pseudonymized_datum, - randomness, - )?; - - blind_column_data.push(( - blinded_pseudonymized_datum.blinded_handle.0, - blinded_pseudonymized_datum.encrypted_data_value.value, - )); - } - - let mut blind_column = Column::new(table.column().attribute(), blind_column_data); - blind_column.sort(); - - let blind_table = BlindTable::new(table.identifier(), vec![blind_column]); - blind_columns.push(blind_table) - } - Ok(blind_columns) +) -> Result, Error> { + let mut blinded_data = pseudonymized_table + .data() + .iter() + .map(|entry| { + blind_pseudonymized_datum(store_context, &bpk_receiver, ek_receiver, entry, randomness) + }) + .collect::, Error>>()?; + + blinded_data.sort(); + Ok(Table::new( + pseudonymized_table.identifier().into(), + blinded_data, + )) } pub fn join_identifier(identifier: String) -> String { @@ -111,61 +55,47 @@ pub fn join_identifier(identifier: String) -> String { join_identifier } -/// ### Conversion -/// Join requests are processed by blindly converting coPRF outputs to a -/// fresh-per-session join evaluation key. +/// ## Oblivious Conversion +/// +/// Obliviously convert a table of blinded pseudonymous data values to fresh +/// join-pseudonyms by applying the pseudonym conversion transformation to +/// each entry and shuffling the result. /// -/// For each of the blinded columns sent for joining by the lake, the -/// pseudonymous column table key is blindly converted to a fresh join -/// evaluation key. +/// Inputs: +/// - `converter_context`: The Converter's coPRF conversion context +/// - `bpk_receiver`: The receiver's public blinding key +/// - `ek_receiver`: The receiver's public encryption key +/// - `table`: A table of blinded pseudonymous data values +/// - `randomness`: Random bytes /// -pub fn join_conversion( +/// Outputs: +/// A table of consistently join-pseudonymized data values. +pub fn convert_blinded_table( converter_context: &ConverterContext, bpk_receiver: BlindingPublicKey, ek_receiver: &HpkePublicKey, - tables: Vec, + table: Table, randomness: &mut Randomness, -) -> Result, Error> { - let mut converted_tables = Vec::new(); +) -> Result, Error> { let conversion_target = randomness.bytes(SECPAR_BYTES)?.to_owned(); + let mut converted_data = table + .data() + .iter() + .map(|entry| { + convert_blinded_datum( + &converter_context.coprf_context, + &bpk_receiver, + ek_receiver, + &conversion_target, + entry, + randomness, + ) + }) + .collect::, Error>>()?; - for table in tables { - for blind_column in table.columns() { - let attribute = blind_column.attribute(); - - let mut converted_data = Vec::new(); - for (blind_identifier, encrypted_value) in blind_column.data() { - let blinded_pseudonymized_datum = BlindedPseudonymizedData { - blinded_handle: BlindedPseudonymizedHandle(blind_identifier), - encrypted_data_value: EncryptedDataValue { - attribute_name: attribute.clone(), - value: encrypted_value, - encryption_level: 1u8, - }, - }; - let blinded_pseudonymized_datum = convert_blinded_datum( - &converter_context.coprf_context, - &bpk_receiver, - &ek_receiver, - &conversion_target, - &blinded_pseudonymized_datum, - randomness, - )?; + converted_data.sort(); - converted_data.push(( - blinded_pseudonymized_datum.blinded_handle.0, - blinded_pseudonymized_datum.encrypted_data_value.value, - )); - } - let mut converted_table_column = Column::new(attribute.clone(), converted_data); - converted_table_column.sort(); - converted_tables.push(ConvertedTable::new( - join_identifier(table.identifier()), - converted_table_column, - )); - } - } - Ok(converted_tables) + Ok(Table::new(table.identifier().into(), converted_data)) } #[cfg(test)] @@ -194,7 +124,7 @@ mod tests { let (lake_ek, lake_bpk) = lake_context.public_keys(); // == Blind Table for Pseudonymization == - let blind_table = crate::split::prepare_split_conversion( + let blind_table = crate::split::blind_orthonymous_table( &lake_ek, lake_bpk, plain_table.clone(), @@ -203,7 +133,7 @@ mod tests { .unwrap(); // == Blind Pseudonymized Table == - let converted_tables = crate::split::split_conversion( + let converted_tables = crate::split::pseudonymize_blinded_table( &converter_context, lake_bpk, &lake_ek, @@ -214,39 +144,46 @@ mod tests { // == Unblinded Pseudonymized Table == let lake_tables = - crate::finalize::finalize_conversion(&lake_context, converted_tables).unwrap(); - - let plain_values: Vec>> = plain_table - .clone() - .columns() - .iter() - .map(|column| HashSet::from_iter(column.values())) - .collect(); + crate::finalize::finalize_blinded_table(&lake_context, converted_tables).unwrap(); let mut pseudonym_set = HashSet::new(); - for table in lake_tables.clone() { + for entry in lake_tables.data() { // store lake_pseudonyms for test against join pseudonyms - for key in table.keys() { - pseudonym_set.insert(key); - } + pseudonym_set.insert(entry.handle.clone()); } // select first two lake tables for join - let join_tables = vec![lake_tables[0].clone(), lake_tables[1].clone()]; + let join_table = Table::new( + "Join".into(), + lake_tables + .data() + .iter() + .filter_map(|entry| { + if entry.data_value.attribute_name == "Address" + || entry.data_value.attribute_name == "Favorite Color" + { + Some(entry.clone()) + } else { + None + } + }) + .collect(), + ); + let processor_context = StoreContext::setup(&mut randomness).unwrap(); let (ek_processor, bpk_processor) = processor_context.public_keys(); - let blind_tables = crate::join::prepare_join_conversion( + let blind_tables = crate::join::blind_pseudonymous_table( &lake_context, bpk_processor, &ek_processor, - join_tables, + join_table, &mut randomness, ) .unwrap(); - let converted_join_tables = crate::join::join_conversion( + let converted_join_tables = crate::join::convert_blinded_table( &converter_context, bpk_processor, &ek_processor, @@ -256,23 +193,24 @@ mod tests { .unwrap(); let joined_tables = - crate::finalize::finalize_conversion(&processor_context, converted_join_tables) + crate::finalize::finalize_blinded_table(&processor_context, converted_join_tables) .unwrap(); - for table in joined_tables { + for entry in joined_tables.data() { let mut lake_pseudonyms = pseudonym_set.clone(); // test if all pseudonyms are fresh compared to lake_pseudonyms - for key in table.keys() { - assert!( - lake_pseudonyms.insert(key), - "Generated pseudonyms are not unique." - ); - } - let table_values: HashSet> = HashSet::from_iter(table.values()); assert!( - plain_values.iter().any(|set| { *set == table_values }), + lake_pseudonyms.insert(entry.handle.clone()), + "Generated pseudonyms are not unique." + ); + + assert!( + plain_table + .data() + .iter() + .any(|entry| entry.data_value == entry.data_value), "Data was not preserved during join." ); } diff --git a/hacspec-scrambledb/scrambledb/src/setup.rs b/hacspec-scrambledb/scrambledb/src/setup.rs index 2971ba1..0d41c7f 100644 --- a/hacspec-scrambledb/scrambledb/src/setup.rs +++ b/hacspec-scrambledb/scrambledb/src/setup.rs @@ -11,8 +11,8 @@ use oprf::coprf::{ use p256::P256Point; use crate::{ + data_types::{BlindedPseudonymizedHandle, FinalizedPseudonym}, error::Error, - table::{BlindPseudonym, Pseudonym}, }; pub struct ConverterContext { @@ -131,9 +131,16 @@ impl StoreContext { /// context.coprf_receiver_context.finalize(blind_pseudonym); /// return PRP.eval(context.k_prp, raw_pseudonym) /// ``` - pub fn finalize_pseudonym(&self, blind_pseudonym: BlindPseudonym) -> Result { - let raw_pseudonym = coprf_online::finalize(&self.coprf_receiver_context, blind_pseudonym)?; - Ok(prp::prp(raw_pseudonym.raw_bytes(), &self.k_prp)) + pub fn finalize_pseudonym( + &self, + blind_pseudonym: BlindedPseudonymizedHandle, + ) -> Result { + let raw_pseudonym = + coprf_online::finalize(&self.coprf_receiver_context, blind_pseudonym.0)?; + Ok(FinalizedPseudonym(prp::prp( + raw_pseudonym.raw_bytes(), + &self.k_prp, + ))) } /// - Recover Raw Pseudonym: In preparation of a join conversion, the raw @@ -152,7 +159,7 @@ impl StoreContext { /// fn recover_raw_pseudonym(context, pseudonym): /// return PRP.invert(context.k_prp, pseudonym) /// ``` - pub fn recover_raw_pseudonym(&self, pseudonym: Pseudonym) -> Result { - P256Point::from_raw_bytes(prp::prp(pseudonym, &self.k_prp)).map_err(|e| e.into()) + pub fn recover_raw_pseudonym(&self, pseudonym: FinalizedPseudonym) -> Result { + P256Point::from_raw_bytes(prp::prp(pseudonym.0, &self.k_prp)).map_err(|e| e.into()) } } diff --git a/hacspec-scrambledb/scrambledb/src/split.rs b/hacspec-scrambledb/scrambledb/src/split.rs index 3c08a9c..934c0ee 100644 --- a/hacspec-scrambledb/scrambledb/src/split.rs +++ b/hacspec-scrambledb/scrambledb/src/split.rs @@ -1,125 +1,87 @@ -//! # Split Conversion +//! # Pseudonymization use hacspec_lib::Randomness; use libcrux::hpke::HpkePublicKey; use oprf::coprf::coprf_setup::BlindingPublicKey; use crate::{ data_transformations::{blind_identifiable_datum, pseudonymize_blinded_datum}, - data_types::{BlindedIdentifiableData, DataValue, EncryptedDataValue, IdentifiableData}, + data_types::{BlindedIdentifiableData, BlindedPseudonymizedData, IdentifiableData}, error::Error, setup::ConverterContext, - table::{BlindTable, Column, ConvertedTable, PlainTable}, + table::Table, }; -fn split_identifier(identifier: String, attribute: String) -> String { - let mut split_identifier = identifier; - split_identifier.push('-'); - split_identifier.push_str(&attribute); - split_identifier -} - -/// ## Preparation +/// ## Blinding Orthonymous Tables +/// +/// Prepare a table of orthonymous data values for pseudonymization by applying +/// the blinding operation on each entry and shuffling the result. /// -/// - For each column of the table, go entry by entry, blinding the table key for the -/// data lake as coPRF receiver and additionaly encrypting the entry value towards the -/// data lake -/// - Sort each column by the blinded table keys (this implements a random shuffle) -pub fn prepare_split_conversion( +/// Inputs: +/// - `ek_receiver`: The receiver's public encryption key +/// - `bpk_receiver`: The receiver's public blinding key +/// - `table`: A table of identifiable data values +/// - `randomness`: Random bytes +/// +/// Outputs: +/// A table of blinded identifiable data. +pub fn blind_orthonymous_table( ek_receiver: &HpkePublicKey, bpk_receiver: BlindingPublicKey, - table: PlainTable, + table: Table, randomness: &mut Randomness, -) -> Result { - let mut blinded_columns = Vec::new(); - - for column in table.columns() { - let attribute = column.attribute(); - - let mut blinded_column_data = Vec::new(); - - for (plaintext_id, plaintext_value) in column.data() { - let datum = IdentifiableData { - handle: plaintext_id, - data_value: DataValue { - attribute_name: attribute.clone(), - value: plaintext_value, - }, - }; - let blinded_datum = - blind_identifiable_datum(&bpk_receiver, ek_receiver, &datum, randomness)?; - - blinded_column_data.push(( - blinded_datum.blinded_handle.0, - blinded_datum.encrypted_data_value.value, - )); - } - let mut blinded_column = Column::new(attribute, blinded_column_data); - blinded_column.sort(); +) -> Result, Error> { + let mut blinded_table_entries = table + .data() + .iter() + .map(|entry| blind_identifiable_datum(&bpk_receiver, ek_receiver, entry, randomness)) + .collect::, Error>>()?; - blinded_columns.push(blinded_column); - } - Ok(BlindTable::new(table.identifier(), blinded_columns)) + blinded_table_entries.sort(); + + Ok(Table::new(table.identifier().into(), blinded_table_entries)) } -/// ## Conversion -/// One part of the joint creation of pseudonomized and unlinkable data to -/// be fed into the data lake. The input table is part of a -/// pseudonymization request by a data source. Its data contents are -/// encrypted towards the data lake and the keys (unpseudonymized -/// identifiers) are blinded to allow conversion. +/// ## Oblivious Pseudonymization +/// +/// Obliviously pseudonymize a table of blinded orthonymous data values by +/// applying the oblivious pseudonymization operation on each entry and +/// shuffling the result. +/// +/// Inputs: +/// - `converter_context`: The Converter's coPRF evaluation context +/// - `ek_receiver`: The receiver's public encryption key +/// - `bpk_receiver`: The receiver's public blinding key +/// - `blinded_table`: A table of blinded identifiable data values +/// - `randomness`: Random bytes /// -/// The output tables are to be fed into the data lake. Each table -/// corresponds to one column (one data attribute) of the original -/// table. All table entries have been assigned pseudonymized keys. In -/// addition the entry ciphertexts have been rerandomized and table rows -/// have been shuffled to prevent correlation of the incoming with the -/// outgoing table data. -pub fn split_conversion( +/// Outputs: +/// A table of blinded pseudonymized data. +pub fn pseudonymize_blinded_table( converter_context: &ConverterContext, bpk_receiver: BlindingPublicKey, ek_receiver: &HpkePublicKey, - blinded_table: BlindTable, + blinded_table: Table, randomness: &mut Randomness, -) -> Result, Error> { - let mut converted_tables = Vec::new(); - - for blinded_column in blinded_table.columns() { - let attribute = blinded_column.attribute(); - - let mut converted_column_data = Vec::new(); - for (blind_identifier, encrypted_value) in blinded_column.data() { - let blinded_datum = BlindedIdentifiableData { - blinded_handle: crate::data_types::BlindedIdentifiableHandle(blind_identifier), - encrypted_data_value: EncryptedDataValue { - attribute_name: attribute.clone(), - value: encrypted_value, - encryption_level: 1u8, - }, - }; - - let blinded_pseudonymized_datum = pseudonymize_blinded_datum( +) -> Result, Error> { + let mut blinded_pseudonymized_entries = blinded_table + .data() + .iter() + .map(|entry| { + pseudonymize_blinded_datum( &converter_context.coprf_context, &bpk_receiver, ek_receiver, - &blinded_datum, + entry, randomness, - )?; - - converted_column_data.push(( - blinded_pseudonymized_datum.blinded_handle.0, - blinded_pseudonymized_datum.encrypted_data_value.value, - )); - } - - let mut converted_column = Column::new(attribute.clone(), converted_column_data); - converted_column.sort(); - converted_tables.push(ConvertedTable::new( - split_identifier(blinded_table.identifier(), attribute), - converted_column, - )); - } - - Ok(converted_tables) + ) + }) + .collect::, Error>>()?; + blinded_pseudonymized_entries.sort(); + + Ok(Table::new( + blinded_table.identifier().into(), + blinded_pseudonymized_entries, + )) } #[cfg(test)] @@ -148,7 +110,7 @@ mod tests { let (lake_ek, lake_bpk) = lake_context.public_keys(); // == Blind Table for Pseudonymization == - let blind_table = crate::split::prepare_split_conversion( + let blind_table = crate::split::blind_orthonymous_table( &lake_ek, lake_bpk, plain_table.clone(), @@ -157,7 +119,7 @@ mod tests { .unwrap(); // == Blind Pseudonymized Table == - let converted_tables = crate::split::split_conversion( + let converted_tables = crate::split::pseudonymize_blinded_table( &converter_context, lake_bpk, &lake_ek, @@ -168,31 +130,25 @@ mod tests { // == Unblinded Pseudonymized Table == let lake_tables = - crate::finalize::finalize_conversion(&lake_context, converted_tables).unwrap(); - - let plain_values: Vec>> = plain_table - .clone() - .columns() - .iter() - .map(|column| HashSet::from_iter(column.values())) - .collect(); + crate::finalize::finalize_blinded_table(&lake_context, converted_tables).unwrap(); let mut pseudonym_set = HashSet::new(); // test that data is preserved - for table in lake_tables { - let table_values: HashSet> = HashSet::from_iter(table.values()); + for pseudonymized_data in lake_tables.data() { assert!( - plain_values.iter().any(|set| { *set == table_values }), + // plain_values.iter().any(|set| { *set == table_values }), + plain_table + .data() + .iter() + .any(|entry| entry.data_value == pseudonymized_data.data_value), "Data was not preserved during pseudonymization." ); // test if all pseudonyms are unique - for key in table.keys() { - assert!( - pseudonym_set.insert(key), - "Generated pseudonyms are not unique." - ); - } + assert!( + pseudonym_set.insert(pseudonymized_data.handle.clone()), + "Generated pseudonyms are not unique." + ); } } } diff --git a/hacspec-scrambledb/scrambledb/src/table.rs b/hacspec-scrambledb/scrambledb/src/table.rs index af5e7b3..ae28be5 100644 --- a/hacspec-scrambledb/scrambledb/src/table.rs +++ b/hacspec-scrambledb/scrambledb/src/table.rs @@ -2,182 +2,33 @@ //! The `ScrambleDB` protocol provides conversions between types of data //! tables that are differentiated by their structure and contents. -use oprf::coprf::coprf_online::{BlindInput, BlindOutput}; - -/// A plain entity identifier is a unicode string. -pub type PlainIdentifier = String; - -/// A plain data value is a plaintext value of the underlying -/// rerandomizable public key encryption scheme. -pub type PlainValue = Vec; -pub type EncryptedValue = Vec; -pub type BlindIdentifier = BlindInput; -pub type BlindPseudonym = BlindOutput; -pub type Pseudonym = [u8; 64]; - -pub type PlainTable = MultiColumnTable; -pub type BlindTable = MultiColumnTable; -pub type ConvertedTable = SingleColumnTable; -pub type PseudonymizedTable = SingleColumnTable; - -use std::fmt::Debug; - -#[derive(Clone, Debug)] -pub struct Column { - attribute: String, - data: Vec<(K, V)>, -} - -impl Column { - pub fn new(attribute: String, data: Vec<(K, V)>) -> Self { - Self { attribute, data } - } - - pub fn len(&self) -> usize { - self.data.len() - } - - pub fn attribute(&self) -> String { - self.attribute.clone() - } - - pub fn keys(&self) -> Vec - where - K: Clone, - { - self.data.iter().map(|(k, _v)| k.clone()).collect() - } - - pub fn values(&self) -> Vec - where - K: Clone, - V: Clone, - { - self.data.iter().map(|(_k, v)| v.clone()).collect() - } - - pub fn get(&self, key: &K) -> Option - where - K: PartialEq, - V: Clone, - { - self.data - .iter() - .find(|(k, _v)| k == key) - .map(|(_k, v)| v.clone()) - } - - pub fn data(&self) -> Vec<(K, V)> - where - K: Clone, - V: Clone, - { - self.data.clone() - } - - pub fn sort(&mut self) - where - K: Ord, - K: Clone, - { - self.data.sort_by_key(|(k, _)| k.clone()) - } -} - -#[derive(Clone, Debug)] -pub struct MultiColumnTable { - identifier: String, - columns: Vec>, -} - -#[derive(Clone)] -pub struct SingleColumnTable { +#[derive(Debug, Clone)] +pub struct Table { identifier: String, - column: Column, -} - -impl SingleColumnTable { - pub fn new(identifier: String, column: Column) -> Self { - Self { identifier, column } - } - pub fn identifier(&self) -> String { - self.identifier.clone() - } - - pub fn column(&self) -> Column - where - K: Clone, - V: Clone, - { - self.column.clone() - } - - pub fn len(&self) -> usize - where - K: Clone, - V: Clone, - { - self.column.data().len() - } - - pub fn keys(&self) -> Vec - where - K: Clone, - V: Clone, - { - self.column.data().iter().map(|(k, _v)| k.clone()).collect() - } - - pub fn values(&self) -> Vec - where - K: Clone, - V: Clone, - { - self.column.data().iter().map(|(_k, v)| v.clone()).collect() - } + data: Vec, } -impl MultiColumnTable { - pub fn new(identifier: String, columns: Vec>) -> Self { - Self { - identifier, - columns, - } - } - pub fn identifier(&self) -> String { - self.identifier.clone() +impl Table { + /// Create a new table. + pub fn new(identifier: String, data: Vec) -> Self { + Self { identifier, data } } - pub fn num_rows(&self) -> usize - where - K: Clone, - V: Clone, - { - self.columns()[0].len() + /// Get the identifier (name) of this table. + pub fn identifier(&self) -> &str { + &self.identifier } - pub fn columns(&self) -> Vec> - where - K: Clone, - V: Clone, - { - self.columns.clone() + /// Get the table entries. + pub fn data(&self) -> &[T] { + self.data.as_ref() } - pub fn rows(&self) -> Vec> + /// Sort the table by its handles. + pub fn sort(&mut self) where - K: Clone + PartialEq, - V: Clone, + T: Ord, { - let mut rows = Vec::new(); - for i in 0..self.num_rows() { - let mut row = Vec::new(); - for column in self.columns() { - row.push(column.data()[i].clone()); - } - rows.push(row) - } - - rows + self.data.sort() } } diff --git a/hacspec-scrambledb/scrambledb/src/test_util.rs b/hacspec-scrambledb/scrambledb/src/test_util.rs index d76817c..9cef69e 100644 --- a/hacspec-scrambledb/scrambledb/src/test_util.rs +++ b/hacspec-scrambledb/scrambledb/src/test_util.rs @@ -1,30 +1,52 @@ -use crate::table::{Column, PlainTable}; +use crate::{ + data_types::{DataValue, IdentifiableData}, + table::Table, +}; -pub fn generate_plain_table() -> PlainTable { - let mut columns = Vec::new(); - columns.push(Column::new( - String::from("Address"), - vec![ - (String::from("Alice"), b"TestData1".to_vec()), - (String::from("Bob"), b"TestData2".to_vec()), - ], - )); +pub fn generate_plain_table() -> Table { + let mut data = Vec::new(); + data.push(IdentifiableData { + handle: "Alice".into(), + data_value: DataValue { + value: b"TestData1".to_vec(), + attribute_name: "Address".into(), + }, + }); + data.push(IdentifiableData { + handle: "Bob".into(), + data_value: DataValue { + value: b"TestData2".to_vec(), + attribute_name: "Address".into(), + }, + }); + data.push(IdentifiableData { + handle: "Alice".into(), + data_value: DataValue { + value: b"TestData3".to_vec(), + attribute_name: "Date of Birth".into(), + }, + }); + data.push(IdentifiableData { + handle: "Bob".into(), + data_value: DataValue { + value: b"TestData4".to_vec(), + attribute_name: "Date of Birth".into(), + }, + }); + data.push(IdentifiableData { + handle: "Alice".into(), + data_value: DataValue { + value: b"TestData5".to_vec(), + attribute_name: "Favorite Color".into(), + }, + }); + data.push(IdentifiableData { + handle: "Bob".into(), + data_value: DataValue { + value: b"TestData6".to_vec(), + attribute_name: "Favorite Color".into(), + }, + }); - columns.push(Column::new( - String::from("Date of Birth"), - vec![ - (String::from("Alice"), b"TestData3".to_vec()), - (String::from("Bob"), b"TestData4".to_vec()), - ], - )); - - columns.push(Column::new( - String::from("Favorite Color"), - vec![ - (String::from("Alice"), b"TestData5".to_vec()), - (String::from("Bob"), b"TestData6".to_vec()), - ], - )); - - PlainTable::new(String::from("ExampleTable"), columns) + Table::new(String::from("ExampleTable"), data) } diff --git a/hacspec-scrambledb/scrambledb/src/wasm_demo.rs b/hacspec-scrambledb/scrambledb/src/wasm_demo.rs index 2e91b47..7e2f5c9 100644 --- a/hacspec-scrambledb/scrambledb/src/wasm_demo.rs +++ b/hacspec-scrambledb/scrambledb/src/wasm_demo.rs @@ -2,21 +2,24 @@ use hacspec_lib::Randomness; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use web_sys::HtmlTableRowElement; + use web_sys::{Document, HtmlTableElement}; -use crate::table::BlindIdentifier; -use crate::table::Column; -use crate::table::ConvertedTable; -use crate::table::EncryptedValue; -use crate::table::MultiColumnTable; -use crate::table::Pseudonym; -use crate::table::SingleColumnTable; +use crate::data_types::BlindedIdentifiableData; +use crate::data_types::BlindedIdentifiableHandle; +use crate::data_types::BlindedPseudonymizedData; +use crate::data_types::BlindedPseudonymizedHandle; +use crate::data_types::DataValue; +use crate::data_types::EncryptedDataValue; +use crate::data_types::FinalizedPseudonym; +use crate::data_types::IdentifiableData; +use crate::data_types::PseudonymizedData; use crate::{ setup::{ConverterContext, StoreContext}, - table::{BlindTable, PlainTable, PseudonymizedTable}, + table::Table, }; -use std::fmt::Debug; + +use std::fmt::Display; use gloo_utils::format::JsValueSerdeExt; @@ -30,22 +33,27 @@ pub fn init_table(table: JsValue) { run(table) } -pub fn generate_plain_table(table: serde_json::Value) -> PlainTable { - let mut columns = Vec::new(); - let column_names = ["Address", "Date of Birth", "Favourite Color"]; - for column in column_names { - let mut column_values = vec![]; +const DEMO_COLUMN_NAMES: [&str; 3] = ["Address", "Date of Birth", "Favourite Color"]; + +pub fn generate_plain_table(table: serde_json::Value) -> Table { + let mut data = Vec::new(); + for column in DEMO_COLUMN_NAMES { for i in 0..table.as_array().unwrap().len() { let row = &table[i]; let encoded_value = row[column].as_str().unwrap().as_bytes().to_vec(); - column_values.push((row["Identity"].as_str().unwrap().to_string(), encoded_value)); + data.push(IdentifiableData { + handle: row["Identity"].as_str().unwrap().to_string(), + data_value: DataValue { + value: encoded_value, + attribute_name: column.into(), + }, + }); } - columns.push(Column::new(column.to_string(), column_values)); } - PlainTable::new(String::from("ExampleTable"), columns) + Table::new("ExampleTable".into(), data) } pub fn run(table: serde_json::Value) { @@ -68,7 +76,7 @@ pub fn run(table: serde_json::Value) { let (ek_processor, bpk_processor) = processor_context.public_keys(); // Split conversion - let blind_source_table = crate::split::prepare_split_conversion( + let blind_source_table = crate::split::blind_orthonymous_table( &ek_lake, bpk_lake, source_table.clone(), @@ -76,7 +84,7 @@ pub fn run(table: serde_json::Value) { ) .unwrap(); - let blind_split_tables = crate::split::split_conversion( + let blind_split_tables = crate::split::pseudonymize_blinded_table( &converter_context, bpk_lake, &ek_lake, @@ -86,15 +94,27 @@ pub fn run(table: serde_json::Value) { .unwrap(); let finalized_split_tables = - crate::finalize::finalize_conversion(&lake_context, blind_split_tables.clone()).unwrap(); + crate::finalize::finalize_blinded_table(&lake_context, blind_split_tables.clone()).unwrap(); // Join conversion - let join_table_selection = vec![ - finalized_split_tables[0].clone(), - finalized_split_tables[1].clone(), - ]; + let join_table_selection = Table::new( + "Join".into(), + finalized_split_tables + .data() + .iter() + .filter_map(|entry| { + if entry.data_value.attribute_name == DEMO_COLUMN_NAMES[0] + || entry.data_value.attribute_name == DEMO_COLUMN_NAMES[1] + { + Some(entry.clone()) + } else { + None + } + }) + .collect(), + ); - let blind_pre_join_tables = crate::join::prepare_join_conversion( + let blind_pre_join_tables = crate::join::blind_pseudonymous_table( &lake_context, bpk_processor, &ek_processor, @@ -103,7 +123,7 @@ pub fn run(table: serde_json::Value) { ) .unwrap(); - let blind_joined_tables = crate::join::join_conversion( + let blind_joined_tables = crate::join::convert_blinded_table( &converter_context, bpk_processor, &ek_processor, @@ -113,7 +133,7 @@ pub fn run(table: serde_json::Value) { .unwrap(); let joined_tables = - crate::finalize::finalize_conversion(&processor_context, blind_joined_tables.clone()) + crate::finalize::finalize_blinded_table(&processor_context, blind_joined_tables.clone()) .unwrap(); // == Visualization == @@ -126,51 +146,81 @@ pub fn run(table: serde_json::Value) { // dom_insert_multicolumn_table(&"data-source-table-plain", &source_table, &document); // fill_plain_table(&source_table_dom, &source_table); - // == Blind Table for Pseudonymization == - let converter_input_1 = dom_insert_multicolumn_table_single_id( - &"converter-input-1", - &blind_source_table, - &document, - ); - fill_blind_table_single_id(&converter_input_1, &blind_source_table); + for column in DEMO_COLUMN_NAMES { + // Converter Input + let converter_input_1 = dom_insert_column_table(&"converter-input-1", column, &document); + fill_blind_column( + &converter_input_1, + blind_source_table + .data() + .iter() + .filter(|entry| entry.encrypted_data_value.attribute_name == column) + .collect(), + ); - // == Blind Pseudonymized Table == - for converted_table in blind_split_tables.iter() { - let table_element = - dom_insert_column_table(&"converter-output-1", &converted_table, &document); - fill_blind_column(&table_element, converted_table); - } + let converted_table_element = + dom_insert_column_table(&"converter-output-1", column, &document); + fill_blinded_pseudonymized_column( + &converted_table_element, + blind_split_tables + .data() + .iter() + .filter(|entry| entry.encrypted_data_value.attribute_name == column) + .collect(), + ); - // == Unblinded Pseudonymized Table == - for lake_table in finalized_split_tables.iter() { - let lake_table_element = - dom_insert_column_table(&"data-lake-tables", &lake_table, &document); - fill_pseudonymized_column(&lake_table_element, lake_table); + let lake_table_element = dom_insert_column_table(&"data-lake-tables", &column, &document); + fill_pseudonymized_column( + &lake_table_element, + finalized_split_tables + .data() + .iter() + .filter(|entry| entry.data_value.attribute_name == column) + .collect(), + ); } - // select first two lake tables for join - for table in blind_pre_join_tables.iter() { - let converter_input_2 = - dom_insert_multicolumn_table(&"converter-input-2", &table, &document); - fill_blind_table(&converter_input_2, &table); - } + for column in DEMO_COLUMN_NAMES[0..2].iter() { + let converter_input_element_2 = + dom_insert_column_table(&"converter-input-2", column, &document); + + fill_blinded_pseudonymized_column( + &converter_input_element_2, + blind_pre_join_tables + .data() + .iter() + .filter(|entry| entry.encrypted_data_value.attribute_name == *column) + .collect(), + ); - for table in blind_joined_tables.iter() { - let converter_output_2 = dom_insert_column_table(&"converter-output-2", &table, &document); - fill_blind_column(&converter_output_2, &table); - } + let converter_output_element_2 = + dom_insert_column_table(&"converter-output-2", column, &document); + fill_blinded_pseudonymized_column( + &converter_output_element_2, + blind_joined_tables + .data() + .iter() + .filter(|entry| entry.encrypted_data_value.attribute_name == *column) + .collect(), + ); - for lake_table in joined_tables.iter() { let lake_table_element = - dom_insert_column_table(&"data-processor-joined", &lake_table, &document); - fill_pseudonymized_column(&lake_table_element, lake_table); + dom_insert_column_table(&"data-processor-joined", &column, &document); + fill_pseudonymized_column( + &lake_table_element, + joined_tables + .data() + .iter() + .filter(|entry| entry.data_value.attribute_name == *column) + .collect(), + ); } } -// Create a table skeleton for pseudonymous table -fn dom_insert_column_table( +// Create a table skeleton for a table skeleton +fn dom_insert_column_table( element_id: &str, - table: &SingleColumnTable, + header: &str, document: &Document, ) -> HtmlTableElement { let table_div = document.get_element_by_id(element_id).unwrap(); @@ -193,7 +243,7 @@ fn dom_insert_column_table( let id_cell = header_row.insert_cell().unwrap(); id_cell.set_text_content(Some(&"ID")); let header_cell = header_row.insert_cell().unwrap(); - header_cell.set_text_content(Some(&table.column().attribute())); + header_cell.set_text_content(Some(header)); t_head.append_child(&header_row).unwrap(); @@ -201,188 +251,96 @@ fn dom_insert_column_table( table_element } -// creates a table skeleton for a multicolumn table. -fn dom_insert_multicolumn_table( - element_id: &str, - table: &MultiColumnTable, - document: &Document, -) -> HtmlTableElement -where - K: Clone, - V: Clone, -{ - let table_div = document.get_element_by_id(element_id).unwrap(); - - let table_element: HtmlTableElement = document - .create_element("table") - .unwrap() - .dyn_into::() - .unwrap(); - - let t_head = table_element.create_t_head(); - - let header_row = document - .create_element("tr") - .unwrap() - .dyn_into::() - .unwrap(); - header_row.set_attribute("class", "tableheader").unwrap(); - - for colum in table.columns() { - let id_cell = header_row.insert_cell().unwrap(); - id_cell.set_text_content(Some(&"ID")); - let header_cell = header_row.insert_cell().unwrap(); - header_cell.set_text_content(Some(&colum.attribute())); - } - - t_head.append_child(&header_row).unwrap(); - - table_div.append_child(&table_element).unwrap(); - table_element -} - -// creates a table skeleton for a multicolumn table where only the first id column is shown. -fn dom_insert_multicolumn_table_single_id( - element_id: &str, - table: &MultiColumnTable, - document: &Document, -) -> HtmlTableElement -where - K: Clone, - V: Clone, -{ - let table_div = document.get_element_by_id(element_id).unwrap(); - - let table_element: HtmlTableElement = document - .create_element("table") - .unwrap() - .dyn_into::() - .unwrap(); - - let t_head = table_element.create_t_head(); - - let header_row = document - .create_element("tr") - .unwrap() - .dyn_into::() - .unwrap(); - header_row.set_attribute("class", "tableheader").unwrap(); - - let id_cell = header_row.insert_cell().unwrap(); - id_cell.set_text_content(Some(&"ID")); - - for colum in table.columns() { - let header_cell = header_row.insert_cell().unwrap(); - header_cell.set_text_content(Some(&colum.attribute())); - } - - t_head.append_child(&header_row).unwrap(); - - table_div.append_child(&table_element).unwrap(); - table_element -} -// fn fill_plain_table(table_element: &HtmlTableElement, plain_table: &PlainTable) { -// for row in plain_table.rows() { -// let html_row = table_element -// .insert_row() -// .unwrap() -// .dyn_into::() -// .unwrap(); -// for (key, value) in row { -// insert_cell(&html_row, TableCell::PlainID(key)); -// insert_cell(&html_row, TableCell::PlainValue(value)); -// } -// } -// } - -fn fill_blind_table(table_element: &HtmlTableElement, blind_table: &BlindTable) { - for row in blind_table.rows() { +fn fill_blind_column(table_element: &HtmlTableElement, table_data: Vec<&BlindedIdentifiableData>) { + for blinded_data in table_data { let html_row = table_element .insert_row() .unwrap() .dyn_into::() .unwrap(); - for (key, value) in row { - insert_cell(&html_row, TableCell::BlindID(key)); - insert_cell(&html_row, TableCell::BlindValue(value)); - } + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.blinded_handle.to_string())); + + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.encrypted_data_value.to_string())); } } -fn fill_blind_table_single_id(table_element: &HtmlTableElement, blind_table: &BlindTable) { - for row in blind_table.rows() { +fn fill_blinded_pseudonymized_column( + table_element: &HtmlTableElement, + table_data: Vec<&BlindedPseudonymizedData>, +) { + for blinded_data in table_data { let html_row = table_element .insert_row() .unwrap() .dyn_into::() .unwrap(); - insert_cell(&html_row, TableCell::BlindID(row[0].0)); - insert_cell(&html_row, TableCell::BlindValue(row[0].1.clone())); + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.blinded_handle.to_string())); - for (_key, value) in row.iter().skip(1) { - insert_cell(&html_row, TableCell::BlindValue(value.clone())); - } + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.encrypted_data_value.to_string())); } } -fn fill_pseudonymized_column(table_element: &HtmlTableElement, table: &PseudonymizedTable) { - for (key, value) in table.column().data() { +fn fill_pseudonymized_column( + table_element: &HtmlTableElement, + table_data: Vec<&PseudonymizedData>, +) { + for blinded_data in table_data { let html_row = table_element .insert_row() .unwrap() .dyn_into::() .unwrap(); - insert_cell(&html_row, TableCell::Pseudonym(key)); - insert_cell( - &html_row, - TableCell::PlainValue(String::from_utf8_lossy(&value).to_string()), - ); + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.handle.to_string())); + + let cell = html_row.insert_cell().unwrap(); + cell.set_text_content(Some(&blinded_data.data_value.to_string())); } } -fn fill_blind_column(table_element: &HtmlTableElement, table: &ConvertedTable) { - for (key, value) in table.column().data() { - let html_row = table_element - .insert_row() - .unwrap() - .dyn_into::() - .unwrap(); +impl Display for FinalizedPseudonym { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "NYM({}...)", hex::encode(&self.0[0..5])) + } +} - insert_cell(&html_row, TableCell::BlindID(key)); - insert_cell(&html_row, TableCell::BlindValue(value)); +impl Display for BlindedIdentifiableHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BLIND_ID({}..., {}...)", + hex::encode(&self.0 .0.raw_bytes()[0..5]), + hex::encode(&self.0 .1.raw_bytes()[0..5]), + ) } } -enum TableCell { - PlainValue(String), - BlindID(BlindIdentifier), - BlindValue(EncryptedValue), - Pseudonym(Pseudonym), +impl Display for BlindedPseudonymizedHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "BLIND_NYM({}..., {}...)", + hex::encode(&self.0 .0.raw_bytes()[0..5]), + hex::encode(&self.0 .1.raw_bytes()[0..5]), + ) + } } -impl TableCell { - fn to_string(&self) -> String { - match &self { - TableCell::PlainValue(v) => v.clone(), - TableCell::BlindID(b) => String::from(format!( - "BLIND-ID({}..., {}...)", - &hex::encode(b.0.raw_bytes())[0..5], - &hex::encode(b.1.raw_bytes())[0..5], - )), - TableCell::BlindValue(b) => { - format!("ENC({}...)", &hex::encode(b)[0..5],) - } - TableCell::Pseudonym(nym) => { - String::from(format!("NYM({}...)", &hex::encode(nym)[0..5])) - } - } +impl Display for DataValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", String::from_utf8(self.value.clone()).unwrap()) } } -fn insert_cell(row: &HtmlTableRowElement, value: TableCell) { - let cell = row.insert_cell().unwrap(); - cell.set_text_content(Some(&value.to_string())); +impl Display for EncryptedDataValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ENC({}...)", hex::encode(&self.value[0..5]),) + } } diff --git a/hacspec-scrambledb/scrambledb/wasm_demo/README.md b/hacspec-scrambledb/scrambledb/wasm_demo/README.md index 294ffe4..66c47a7 100644 --- a/hacspec-scrambledb/scrambledb/wasm_demo/README.md +++ b/hacspec-scrambledb/scrambledb/wasm_demo/README.md @@ -1,10 +1,14 @@ +# Prerequisites + +Building the demo requires the [`emscripten` toolchain](https://emscripten.org/) to be installed. + # Running the demo At the `scrambledb` crate root, run the following to build the WASM version of the project in `./pkg`: ```shell -$ wasm-pack build --target web --features wasm +$ CC=emcc AR=emar wasm-pack build --target web --features wasm ``` To serve the project locally, copy `index.html` to the right directory