diff --git a/README.md b/README.md index a7d1a9a1..1f6e0127 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A preprint paper describing the construction of Ferveo and the novel cryptosyste ## Build -A Rust toolchain with version `>= 1.65.0` is required. In the future, Ferveo will target the `stable` toolchain. +A Rust toolchain with version `>= 1.67.0` is required. In the future, Ferveo will target the `stable` toolchain. Installation via [rustup](https://rustup.rs/) is recommended. Run `cargo build --release` to build. diff --git a/ferveo-common/src/lib.rs b/ferveo-common/src/lib.rs index c041b6da..b6ddc7c2 100644 --- a/ferveo-common/src/lib.rs +++ b/ferveo-common/src/lib.rs @@ -19,15 +19,14 @@ impl fmt::Display for Error { Error::InvalidByteLength(expected, actual) => { write!( f, - "Invalid byte length: expected {}, actual {}", - expected, actual + "Invalid byte length: expected {expected}, actual {actual}" ) } Error::SerializationError(e) => { - write!(f, "Serialization error: {}", e) + write!(f, "Serialization error: {e}") } Error::InvalidSeedLength(len) => { - write!(f, "Invalid seed length: {}", len) + write!(f, "Invalid seed length: {len}") } } } diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py index 0916738d..7c433e96 100644 --- a/ferveo-python/examples/server_api_precomputed.py +++ b/ferveo-python/examples/server_api_precomputed.py @@ -82,7 +82,7 @@ def gen_eth_addr(i: int) -> str: # Create a decryption share for the ciphertext decryption_share = aggregate.create_decryption_share_precomputed( - dkg, ciphertext, aad, validator_keypair + dkg, ciphertext.header, aad, validator_keypair ) decryption_shares.append(decryption_share) diff --git a/ferveo-python/examples/server_api_simple.py b/ferveo-python/examples/server_api_simple.py index e41c9f24..4f9e8447 100644 --- a/ferveo-python/examples/server_api_simple.py +++ b/ferveo-python/examples/server_api_simple.py @@ -85,7 +85,7 @@ def gen_eth_addr(i: int) -> str: # Create a decryption share for the ciphertext decryption_share = aggregate.create_decryption_share_simple( - dkg, ciphertext, aad, validator_keypair + dkg, ciphertext.header, aad, validator_keypair ) decryption_shares.append(decryption_share) diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index f89aa778..478628b1 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -9,6 +9,7 @@ Transcript, Dkg, Ciphertext, + CiphertextHeader, DecryptionShareSimple, DecryptionSharePrecomputed, AggregatedTranscript, diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index 1dfab2f0..ff17fd19 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -119,6 +119,9 @@ class Dkg: @final class Ciphertext: + header: CiphertextHeader + payload: bytes + @staticmethod def from_bytes(data: bytes) -> Ciphertext: ... @@ -127,6 +130,16 @@ class Ciphertext: ... +@final +class CiphertextHeader: + @staticmethod + def from_bytes(data: bytes) -> CiphertextHeader: + ... + + def __bytes__(self) -> bytes: + ... + + @final class DecryptionShareSimple: @staticmethod @@ -159,7 +172,7 @@ class AggregatedTranscript: def create_decryption_share_simple( self, dkg: Dkg, - ciphertext: Ciphertext, + ciphertext_header: CiphertextHeader, aad: bytes, validator_keypair: Keypair ) -> DecryptionShareSimple: @@ -168,7 +181,7 @@ class AggregatedTranscript: def create_decryption_share_precomputed( self, dkg: Dkg, - ciphertext: Ciphertext, + ciphertext_header: CiphertextHeader, aad: bytes, validator_keypair: Keypair ) -> DecryptionSharePrecomputed: diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py index 7d93c637..42a82a84 100644 --- a/ferveo-python/test/test_ferveo.py +++ b/ferveo-python/test/test_ferveo.py @@ -91,7 +91,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t assert pvss_aggregated.verify(shares_num, messages) decryption_share = decryption_share_for_variant(variant, pvss_aggregated)( - dkg, ciphertext, aad, validator_keypair + dkg, ciphertext.header, aad, validator_keypair ) decryption_shares.append(decryption_share) diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts index 69ec9f8a..28144861 100644 --- a/ferveo-wasm/examples/node/src/main.test.ts +++ b/ferveo-wasm/examples/node/src/main.test.ts @@ -27,11 +27,11 @@ function setupTest() { const sharesNum = 4; const threshold = Math.floor((sharesNum * 2) / 3); - const validator_keypairs: Keypair[] = []; + const validatorKeypairs: Keypair[] = []; const validators: Validator[] = []; for (let i = 0; i < sharesNum; i++) { const keypair = Keypair.random(); - validator_keypairs.push(keypair); + validatorKeypairs.push(keypair); const validator = new Validator(genEthAddr(i), keypair.publicKey); validators.push(validator); } @@ -66,7 +66,7 @@ function setupTest() { tau, sharesNum, threshold, - validatorKeypairs: validator_keypairs, + validatorKeypairs, validators, dkg, messages, @@ -103,7 +103,7 @@ describe("ferveo-wasm", () => { const decryptionShare = aggregate.createDecryptionShareSimple( dkg, - ciphertext, + ciphertext.header, aad, keypair ); @@ -150,7 +150,7 @@ describe("ferveo-wasm", () => { const decryptionShare = aggregate.createDecryptionSharePrecomputed( dkg, - ciphertext, + ciphertext.header, aad, keypair ); diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs index c1564c82..b4234d07 100644 --- a/ferveo-wasm/tests/node.rs +++ b/ferveo-wasm/tests/node.rs @@ -125,7 +125,7 @@ fn tdec_simple() { aggregate .create_decryption_share_simple( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), &aad, &keypair, ) @@ -179,7 +179,7 @@ fn tdec_precomputed() { aggregate .create_decryption_share_precomputed( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), &aad, &keypair, ) diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs index 00972c05..a6dd9f48 100644 --- a/ferveo/benches/benchmarks/validity_checks.rs +++ b/ferveo/benches/benchmarks/validity_checks.rs @@ -22,7 +22,7 @@ fn gen_keypairs(num: u32) -> Vec> { } pub fn gen_address(i: usize) -> EthereumAddress { - EthereumAddress::from_str(&format!("0x{:040}", i)).unwrap() + EthereumAddress::from_str(&format!("0x{i:040}")).unwrap() } fn gen_validators( diff --git a/ferveo/examples/bench_ark_sizes.rs b/ferveo/examples/bench_ark_sizes.rs index 7b211597..95ad35f6 100644 --- a/ferveo/examples/bench_ark_sizes.rs +++ b/ferveo/examples/bench_ark_sizes.rs @@ -47,8 +47,7 @@ pub fn save_data( let mut file = OpenOptions::new().append(true).open(&file_path).unwrap(); writeln!( file, - "{}|{}|{}|", - n_of_elements, type_of_element, serialized_size_in_bytes + "{n_of_elements}|{type_of_element}|{serialized_size_in_bytes}|" ) .unwrap(); } @@ -66,10 +65,10 @@ fn main() { .map(|(n, element)| (n, element)) .collect::>(); - println!("Running benchmarks for {:?}", configs); + println!("Running benchmarks for {configs:?}"); for (n, element) in configs { - println!("number_of_elements: {}, type_of_elements: {}", n, element); + println!("number_of_elements: {n}, type_of_elements: {element}"); let g1_affine = (0..*n).map(|_| G1Affine::rand(rng)).collect::>(); diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs index e84c64a6..18adf673 100644 --- a/ferveo/examples/bench_primitives_size.rs +++ b/ferveo/examples/bench_primitives_size.rs @@ -42,12 +42,8 @@ pub fn save_data( eprintln!("Appending to file: {}", file_path.display()); let mut file = OpenOptions::new().append(true).open(&file_path).unwrap(); - writeln!( - file, - "{}|{}|{}|", - shares_num, threshold, transcript_size_bytes - ) - .unwrap(); + writeln!(file, "{shares_num}|{threshold}|{transcript_size_bytes}|") + .unwrap(); } // TODO: Find a way to deduplicate the following methods with benchmarks and test setup @@ -60,7 +56,7 @@ fn gen_keypairs(num: u32) -> Vec> { } pub fn gen_address(i: usize) -> EthereumAddress { - EthereumAddress::from_str(&format!("0x{:040}", i)).unwrap() + EthereumAddress::from_str(&format!("0x{i:040}")).unwrap() } fn gen_validators( @@ -132,10 +128,10 @@ fn main() { }) .collect::>(); - println!("Running benchmarks for {:?}", configs); + println!("Running benchmarks for {configs:?}"); for (shares_num, threshold) in configs { - println!("shares_num: {}, threshold: {}", shares_num, threshold); + println!("shares_num: {shares_num}, threshold: {threshold}"); let dkg = setup(*shares_num as u32, threshold, rng); let transcript = &dkg.vss.values().next().unwrap(); let transcript_bytes = bincode::serialize(&transcript).unwrap(); diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index ccf9ff5f..b2179ee6 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; pub use tpke::api::{ prepare_combine_simple, share_combine_precomputed, share_combine_simple, - Ciphertext, Fr, G1Affine, G1Prepared, SecretBox, E, + Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E, }; pub type PublicKey = ferveo_common::PublicKey; @@ -55,7 +55,7 @@ pub fn encrypt( ) -> Result { let mut rng = rand::thread_rng(); let ciphertext = tpke::api::encrypt(message, aad, &pubkey.0, &mut rng)?; - Ok(ciphertext) + Ok(Ciphertext(ciphertext)) } pub fn decrypt_with_shared_secret( @@ -65,7 +65,7 @@ pub fn decrypt_with_shared_secret( ) -> Result> { let dkg_public_params = DkgPublicParameters::default(); tpke::api::decrypt_with_shared_secret( - ciphertext, + &ciphertext.0, aad, &shared_secret.0, &dkg_public_params.g1_inv, @@ -73,6 +73,23 @@ pub fn decrypt_with_shared_secret( .map_err(Error::from) } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Eq)] +pub struct Ciphertext(tpke::api::Ciphertext); + +impl Ciphertext { + pub fn header(&self) -> Result { + Ok(CiphertextHeader(self.0.header()?)) + } + + pub fn payload(&self) -> Vec { + self.0.payload() + } +} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct CiphertextHeader(tpke::api::CiphertextHeader); + /// The ferveo variant to use for the decryption share derivation. #[derive( PartialEq, Eq, Debug, Serialize, Deserialize, Copy, Clone, PartialOrd, @@ -286,7 +303,7 @@ impl AggregatedTranscript { pub fn create_decryption_share_precomputed( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> Result { @@ -297,7 +314,7 @@ impl AggregatedTranscript { .take(dkg.0.dkg_params.shares_num as usize) .collect(); self.0.make_decryption_share_simple_precomputed( - ciphertext, + &ciphertext_header.0, aad, &validator_keypair.decryption_key, dkg.0.me.share_index, @@ -309,12 +326,12 @@ impl AggregatedTranscript { pub fn create_decryption_share_simple( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> Result { let share = self.0.make_decryption_share_simple( - ciphertext, + &ciphertext_header.0, aad, &validator_keypair.decryption_key, dkg.0.me.share_index, @@ -377,7 +394,7 @@ pub struct SharedSecret(pub tpke::api::SharedSecret); #[cfg(test)] mod test_ferveo_api { use itertools::izip; - use rand::{prelude::StdRng, thread_rng, SeedableRng}; + use rand::{prelude::StdRng, SeedableRng}; use tpke::SecretBox; use crate::{api::*, dkg::test_common::*}; @@ -458,14 +475,9 @@ mod test_ferveo_api { // In the meantime, the client creates a ciphertext and decryption request let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); - let rng = &mut thread_rng(); - let ciphertext = tpke::api::encrypt( - SecretBox::new(msg.clone()), - aad, - &dkg_public_key.0, - rng, - ) - .unwrap(); + let ciphertext = + encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key) + .unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares let decryption_shares: Vec<_> = @@ -490,7 +502,7 @@ mod test_ferveo_api { aggregate .create_decryption_share_precomputed( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), aad, validator_keypair, ) @@ -557,14 +569,8 @@ mod test_ferveo_api { // In the meantime, the client creates a ciphertext and decryption request let msg = "my-msg".as_bytes().to_vec(); let aad: &[u8] = "my-aad".as_bytes(); - let rng = &mut thread_rng(); - let ciphertext = tpke::api::encrypt( - SecretBox::new(msg.clone()), - aad, - &public_key.0, - rng, - ) - .unwrap(); + let ciphertext = + encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares let decryption_shares: Vec<_> = @@ -587,7 +593,7 @@ mod test_ferveo_api { aggregate .create_decryption_share_simple( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), aad, validator_keypair, ) diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 7d1cb93b..ed965f3e 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -65,14 +65,12 @@ impl From for PyErr { expected, actual, ) => InsufficientTranscriptsForAggregate::new_err(format!( - "expected: {}, actual: {}", - expected, actual + "expected: {expected}, actual: {actual}" )), Error::InvalidDkgPublicKey => InvalidDkgPublicKey::new_err(""), Error::InsufficientValidators(expected, actual) => { InsufficientValidators::new_err(format!( - "expected: {}, actual: {}", - expected, actual + "expected: {expected}, actual: {actual}" )) } Error::InvalidTranscriptAggregate => { @@ -90,8 +88,7 @@ impl From for PyErr { } Error::InvalidByteLength(expected, actual) => { InvalidByteLength::new_err(format!( - "expected: {}, actual: {}", - expected, actual + "expected: {expected}, actual: {actual}" )) } Error::InvalidVariant(variant) => { @@ -512,8 +509,38 @@ impl Dkg { )] pub struct Ciphertext(api::Ciphertext); +#[pymethods] +impl Ciphertext { + #[getter] + pub fn header(&self) -> PyResult { + let header = self.0.header().map_err(FerveoPythonError::from)?; + Ok(CiphertextHeader(header)) + } + + #[getter] + pub fn payload(&self) -> Vec { + self.0.payload().to_vec() + } +} + generate_bytes_serialization!(Ciphertext); +#[pyclass(module = "ferveo")] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + derive_more::From, + derive_more::AsRef, + derive_more::Into, +)] +pub struct CiphertextHeader(api::CiphertextHeader); + +generate_bytes_serialization!(CiphertextHeader); + #[pyclass(module = "ferveo")] #[derive(Clone, derive_more::AsRef, derive_more::From)] pub struct DecryptionShareSimple(api::DecryptionShareSimple); @@ -558,7 +585,7 @@ impl AggregatedTranscript { pub fn create_decryption_share_precomputed( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> PyResult { @@ -566,7 +593,7 @@ impl AggregatedTranscript { .0 .create_decryption_share_precomputed( &dkg.0, - &ciphertext.0, + &ciphertext_header.0, aad, &validator_keypair.0, ) @@ -577,7 +604,7 @@ impl AggregatedTranscript { pub fn create_decryption_share_simple( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> PyResult { @@ -585,7 +612,7 @@ impl AggregatedTranscript { .0 .create_decryption_share_simple( &dkg.0, - &ciphertext.0, + &ciphertext_header.0, aad, &validator_keypair.0, ) @@ -631,6 +658,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -715,7 +743,7 @@ mod test_ferveo_python { .iter() .enumerate() .map(|(i, keypair)| { - Validator::new(format!("0x{:040}", i), &keypair.public_key()) + Validator::new(format!("0x{i:040}"), &keypair.public_key()) .unwrap() }) .collect(); @@ -802,7 +830,7 @@ mod test_ferveo_python { aggregate .create_decryption_share_precomputed( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), aad, validator_keypair, ) @@ -879,7 +907,7 @@ mod test_ferveo_python { aggregate .create_decryption_share_simple( &dkg, - &ciphertext, + &ciphertext.header().unwrap(), aad, validator_keypair, ) diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index 7b9ae484..e686f8a9 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -30,7 +30,7 @@ pub fn set_panic_hook() { } pub fn map_js_err(err: T) -> Error { - Error::new(&format!("{}", err)) + Error::new(&format!("{err}")) } pub fn to_js_bytes(t: &T) -> Result, Error> { @@ -229,8 +229,36 @@ generate_boxed_bytes_serialization!(FerveoPublicKey, InnerPublicKey); )] pub struct Ciphertext(api::Ciphertext); +#[wasm_bindgen] +impl Ciphertext { + #[wasm_bindgen(js_name = "header", getter)] + pub fn header(&self) -> JsResult { + let header = self.0.header().map_err(map_js_err)?; + Ok(CiphertextHeader(header)) + } + + #[wasm_bindgen(js_name = "payload", getter)] + pub fn payload(&self) -> Vec { + self.0.payload() + } +} + generate_common_methods!(Ciphertext); +#[wasm_bindgen] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + derive_more::From, + derive_more::AsRef, + derive_more::Into, +)] +pub struct CiphertextHeader(api::CiphertextHeader); + +generate_common_methods!(CiphertextHeader); + #[wasm_bindgen(js_name = "ferveoEncrypt")] pub fn ferveo_encrypt( message: &[u8], @@ -497,7 +525,7 @@ impl AggregatedTranscript { pub fn create_decryption_share_precomputed( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> JsResult { @@ -506,7 +534,7 @@ impl AggregatedTranscript { .0 .create_decryption_share_precomputed( &dkg.0, - &ciphertext.0, + &ciphertext_header.0, aad, &validator_keypair.0, ) @@ -518,7 +546,7 @@ impl AggregatedTranscript { pub fn create_decryption_share_simple( &self, dkg: &Dkg, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_keypair: &Keypair, ) -> JsResult { @@ -527,7 +555,7 @@ impl AggregatedTranscript { .0 .create_decryption_share_simple( &dkg.0, - &ciphertext.0, + &ciphertext_header.0, aad, &validator_keypair.0, ) @@ -577,7 +605,7 @@ pub mod test_common { } pub fn gen_address(i: usize) -> EthereumAddress { - EthereumAddress::from_string(&format!("0x{:040}", i)).unwrap() + EthereumAddress::from_string(&format!("0x{i:040}")).unwrap() } pub fn gen_validator(i: usize, keypair: &Keypair) -> Validator { diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 07dee015..ed183b72 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -333,7 +333,7 @@ pub(crate) mod test_common { } pub fn gen_address(i: usize) -> EthereumAddress { - EthereumAddress::from_str(&format!("0x{:040}", i)).unwrap() + EthereumAddress::from_str(&format!("0x{i:040}")).unwrap() } pub fn gen_validators(keypairs: &[Keypair]) -> Vec> { diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 214f9444..1ddd54e0 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -127,8 +127,8 @@ mod test_dkg_full { use ferveo_common::Keypair; use group_threshold_cryptography as tpke; use group_threshold_cryptography::{ - Ciphertext, DecryptionSharePrecomputed, DecryptionShareSimple, - SecretBox, SharedSecret, + DecryptionSharePrecomputed, DecryptionShareSimple, SecretBox, + SharedSecret, }; use itertools::izip; @@ -140,7 +140,7 @@ mod test_dkg_full { fn make_shared_secret_simple_tdec( dkg: &PubliclyVerifiableDkg, aad: &[u8], - ciphertext: &Ciphertext, + ciphertext_header: &tpke::CiphertextHeader, validator_keypairs: &[Keypair], ) -> ( PubliclyVerifiableSS, @@ -159,7 +159,7 @@ mod test_dkg_full { .unwrap(); pvss_aggregated .make_decryption_share_simple( - ciphertext, + ciphertext_header, aad, &validator_keypair.decryption_key, validator.share_index, @@ -211,7 +211,7 @@ mod test_dkg_full { let (_, _, shared_secret) = make_shared_secret_simple_tdec( &dkg, aad, - &ciphertext, + &ciphertext.header().unwrap(), &validator_keypairs, ); @@ -264,7 +264,7 @@ mod test_dkg_full { .unwrap(); pvss_aggregated .make_decryption_share_simple_precomputed( - &ciphertext, + &ciphertext.header().unwrap(), aad, &validator_keypair.decryption_key, validator.share_index, @@ -307,7 +307,7 @@ mod test_dkg_full { make_shared_secret_simple_tdec( &dkg, aad, - &ciphertext, + &ciphertext.header().unwrap(), &validator_keypairs, ); @@ -367,7 +367,7 @@ mod test_dkg_full { let (_, _, old_shared_secret) = make_shared_secret_simple_tdec( &dkg, aad, - &ciphertext, + &ciphertext.header().unwrap(), &validator_keypairs, ); @@ -443,7 +443,7 @@ mod test_dkg_full { .map(|(share_index, validator_keypair)| { pvss_aggregated .make_decryption_share_simple( - &ciphertext, + &ciphertext.header().unwrap(), aad, &validator_keypair.decryption_key, share_index, @@ -459,7 +459,7 @@ mod test_dkg_full { DecryptionShareSimple::create( &new_validator_decryption_key, &new_private_key_share, - &ciphertext, + &ciphertext.header().unwrap(), aad, &dkg.pvss_params.g_inv(), ) @@ -491,7 +491,7 @@ mod test_dkg_full { let (_, _, old_shared_secret) = make_shared_secret_simple_tdec( &dkg, aad, - &ciphertext, + &ciphertext.header().unwrap(), &validator_keypairs, ); @@ -514,7 +514,7 @@ mod test_dkg_full { .map(|(validator_address, validator_keypair)| { pvss_aggregated .refresh_decryption_share( - &ciphertext, + &ciphertext.header().unwrap(), aad, &validator_keypair.decryption_key, validator_address, diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 0d6433fd..547bb16c 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -14,7 +14,7 @@ use serde_with::serde_as; use subproductdomain::fast_multiexp; use tpke::{ prepare_combine_simple, refresh_private_key_share, - update_share_for_recovery, Ciphertext, DecryptionSharePrecomputed, + update_share_for_recovery, CiphertextHeader, DecryptionSharePrecomputed, DecryptionShareSimple, PrivateKeyShare, }; use zeroize::{self, Zeroize, ZeroizeOnDrop}; @@ -329,7 +329,7 @@ impl PubliclyVerifiableSS { pub fn make_decryption_share_simple( &self, - ciphertext: &Ciphertext, + ciphertext: &CiphertextHeader, aad: &[u8], validator_decryption_key: &E::ScalarField, share_index: usize, @@ -349,7 +349,7 @@ impl PubliclyVerifiableSS { pub fn make_decryption_share_simple_precomputed( &self, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_decryption_key: &E::ScalarField, share_index: usize, @@ -366,7 +366,7 @@ impl PubliclyVerifiableSS { share_index, validator_decryption_key, &private_key_share, - ciphertext, + ciphertext_header, aad, &lagrange_coeffs[share_index], g_inv, @@ -376,7 +376,7 @@ impl PubliclyVerifiableSS { pub fn refresh_decryption_share( &self, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], validator_decryption_key: &E::ScalarField, share_index: usize, @@ -396,7 +396,7 @@ impl PubliclyVerifiableSS { DecryptionShareSimple::create( validator_decryption_key, &refreshed_private_key_share, - ciphertext, + ciphertext_header, aad, &dkg.pvss_params.g_inv(), ) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 27f165c4..8456888c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] profile = "default" -channel = "1.65.0" +channel = "1.67.0" components = ["rustfmt", "clippy"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 952c4947..c6ad85ae 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -118,7 +118,11 @@ impl SetupSimple { // Creating decryption shares let decryption_shares: Vec<_> = contexts .iter() - .map(|context| context.create_share(&ciphertext, aad).unwrap()) + .map(|context| { + context + .create_share(&ciphertext.header().unwrap(), aad) + .unwrap() + }) .collect(); let pub_contexts = contexts[0].clone().public_decryption_contexts; @@ -190,7 +194,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) { DecryptionShareSimple::create_unchecked( &ctx.validator_private_key, &ctx.private_key_share, - &setup.shared.ciphertext, + &setup.shared.ciphertext.header().unwrap(), ) }) .collect::>() @@ -206,7 +210,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) { .iter() .map(|context| { context.create_share_precomputed( - &setup.shared.ciphertext, + &setup.shared.ciphertext.header().unwrap(), &setup.shared.aad, ) }) @@ -301,7 +305,7 @@ pub fn bench_share_combine(c: &mut Criterion) { .map(|context| { context .create_share_precomputed( - &setup.shared.ciphertext, + &setup.shared.ciphertext.header().unwrap(), &setup.shared.aad, ) .unwrap() @@ -388,8 +392,7 @@ pub fn bench_ciphertext_validity_checks(c: &mut Criterion) { let mut rng = rng.clone(); let setup = SetupFast::new(shares_num, msg_size, &mut rng); move || { - black_box(check_ciphertext_validity( - &setup.shared.ciphertext, + black_box(setup.shared.ciphertext.check( &setup.shared.aad, &setup.contexts[0].setup_params.g_inv, )) diff --git a/tpke/src/api.rs b/tpke/src/api.rs index 3ebba690..3e283d77 100644 --- a/tpke/src/api.rs +++ b/tpke/src/api.rs @@ -3,6 +3,7 @@ pub type E = ark_bls12_381::Bls12_381; pub type G1Prepared = ::G1Prepared; pub type G1Affine = ::G1Affine; +pub type G2Affine = ::G2Affine; pub type Fr = ark_bls12_381::Fr; pub type PrivateKey = ark_bls12_381::G2Affine; pub type Result = crate::Result; @@ -11,6 +12,8 @@ pub type PrivateDecryptionContextSimple = pub type DecryptionSharePrecomputed = crate::DecryptionSharePrecomputed; pub type DecryptionShareSimple = crate::DecryptionShareSimple; pub type Ciphertext = crate::Ciphertext; + +pub type CiphertextHeader = crate::CiphertextHeader; pub type TargetField = ::TargetField; pub use crate::{ diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index 814a72d2..81f79389 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -2,9 +2,9 @@ use std::ops::Mul; use ark_ec::{pairing::Pairing, AffineRepr}; use ark_ff::{One, UniformRand}; -use ark_serialize::{CanonicalSerialize, Compress}; +use ark_serialize::CanonicalSerialize; use chacha20poly1305::{ - aead::{generic_array::GenericArray, Aead, KeyInit}, + aead::{generic_array::GenericArray, Aead, KeyInit, Payload}, ChaCha20Poly1305, }; use ferveo_common::serialization; @@ -32,28 +32,63 @@ pub struct Ciphertext { } impl Ciphertext { - pub fn check(&self, g_inv: &E::G1Prepared) -> Result { - let hash_g2 = E::G2Prepared::from(self.construct_tag_hash()?); - - Ok(E::multi_pairing( - [self.commitment.into(), g_inv.to_owned()], - [hash_g2, self.auth_tag.into()], - ) - .0 == E::TargetField::one()) + pub fn check(&self, aad: &[u8], g_inv: &E::G1Prepared) -> Result { + self.header()?.check(aad, g_inv) } - fn construct_tag_hash(&self) -> Result { - let mut hash_input = Vec::::new(); - self.commitment.serialize_compressed(&mut hash_input)?; - hash_input.extend_from_slice(&self.ciphertext); + pub fn ciphertext_hash(&self) -> [u8; 32] { + sha256(&self.ciphertext) + } - hash_to_g2(&hash_input) + pub fn header(&self) -> Result> { + Ok(CiphertextHeader { + commitment: self.commitment, + auth_tag: self.auth_tag, + ciphertext_hash: self.ciphertext_hash(), + }) } + pub fn payload(&self) -> Vec { + self.ciphertext.clone() + } +} + +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct CiphertextHeader { + #[serde_as(as = "serialization::SerdeAs")] + pub commitment: E::G1Affine, + #[serde_as(as = "serialization::SerdeAs")] + pub auth_tag: E::G2Affine, + pub ciphertext_hash: [u8; 32], +} + +impl CiphertextHeader { + pub fn check(&self, aad: &[u8], g_inv: &E::G1Prepared) -> Result { + // Implements a variant of the check in section 4.4.2 of the Ferveo paper: + // 'TPKE.CheckCiphertextValidity(U,W,aad)' + // See: https://eprint.iacr.org/2022/898.pdf + // See: https://nikkolasg.github.io/ferveo/tpke.html#to-validate-ciphertext-for-ind-cca2-security + + // H_G2(U, sym_ctxt_digest, aad) + let hash_g2 = E::G2Prepared::from(construct_tag_hash::( + self.commitment, + &self.ciphertext_hash, + aad, + )?); + + let is_ciphertext_valid = E::multi_pairing( + // e(U, H_G2(U, sym_ctxt_digest, aad)) == e(G, W) ==> + // e(U, H_G2(U, sym_ctxt_digest, aad)) * e(G_inv, W) == 1 + [self.commitment.into(), g_inv.to_owned()], + [hash_g2, self.auth_tag.into()], + ) + .0 == E::TargetField::one(); - pub fn serialized_length(&self) -> usize { - self.commitment.serialized_size(Compress::No) - + self.auth_tag.serialized_size(Compress::No) - + self.ciphertext.len() + if is_ciphertext_valid { + Ok(true) + } else { + Err(Error::CiphertextVerificationFailed) + } } } @@ -78,12 +113,19 @@ pub fn encrypt( let nonce = Nonce::from_commitment::(commitment)?; let shared_secret = SharedSecret::(product); + + let payload = Payload { + msg: message.as_secret().as_ref(), + aad, + }; let ciphertext = shared_secret_to_chacha(&shared_secret)? - .encrypt(&nonce.0, message.as_secret().as_ref()) + .encrypt(&nonce.0, payload) .map_err(Error::SymmetricEncryptionError)? .to_vec(); + let ciphertext_hash = sha256(&ciphertext); + // w - let auth_tag = construct_tag_hash::(commitment, &ciphertext, aad)? + let auth_tag = construct_tag_hash::(commitment, &ciphertext_hash, aad)? .mul(rand_element) .into(); @@ -95,60 +137,35 @@ pub fn encrypt( }) } -/// Implements the check section 4.4.2 of the Ferveo paper, 'TPKE.CheckCiphertextValidity(U,W,aad)' -/// See: https://eprint.iacr.org/2022/898.pdf -/// See: https://nikkolasg.github.io/ferveo/tpke.html#to-validate-ciphertext-for-ind-cca2-security -pub fn check_ciphertext_validity( - c: &Ciphertext, - aad: &[u8], - g_inv: &E::G1Prepared, -) -> Result<()> { - // H_G2(U, aad) - let hash_g2 = E::G2Prepared::from(construct_tag_hash::( - c.commitment, - &c.ciphertext[..], - aad, - )?); - - let is_ciphertext_valid = E::multi_pairing( - // e(U, H_G2(U, aad)) = e(G, W) - [c.commitment.into(), g_inv.to_owned()], - [hash_g2, c.auth_tag.into()], - ) - .0 == E::TargetField::one(); - - if is_ciphertext_valid { - Ok(()) - } else { - Err(Error::CiphertextVerificationFailed) - } -} - pub fn decrypt_symmetric( ciphertext: &Ciphertext, aad: &[u8], private_key: &E::G2Affine, g_inv: &E::G1Prepared, ) -> Result> { - check_ciphertext_validity(ciphertext, aad, g_inv)?; + ciphertext.check(aad, g_inv)?; let shared_secret = E::pairing( E::G1Prepared::from(ciphertext.commitment), E::G2Prepared::from(*private_key), ) .0; let shared_secret = SharedSecret(shared_secret); - decrypt_with_shared_secret_unchecked(ciphertext, &shared_secret) + decrypt_with_shared_secret_unchecked(ciphertext, aad, &shared_secret) } fn decrypt_with_shared_secret_unchecked( ciphertext: &Ciphertext, + aad: &[u8], shared_secret: &SharedSecret, ) -> Result> { let nonce = Nonce::from_commitment::(ciphertext.commitment)?; - let ciphertext = ciphertext.ciphertext.to_vec(); - + let ctxt = ciphertext.ciphertext.to_vec(); + let payload = Payload { + msg: ctxt.as_ref(), + aad, + }; let plaintext = shared_secret_to_chacha(shared_secret)? - .decrypt(&nonce.0, ciphertext.as_ref()) + .decrypt(&nonce.0, payload) .map_err(|_| Error::CiphertextVerificationFailed)? .to_vec(); @@ -161,15 +178,15 @@ pub fn decrypt_with_shared_secret( shared_secret: &SharedSecret, g_inv: &E::G1Prepared, ) -> Result> { - check_ciphertext_validity(ciphertext, aad, g_inv)?; - decrypt_with_shared_secret_unchecked(ciphertext, shared_secret) + ciphertext.check(aad, g_inv)?; + decrypt_with_shared_secret_unchecked(ciphertext, aad, shared_secret) } -fn sha256(input: &[u8]) -> Vec { +fn sha256(input: &[u8]) -> [u8; 32] { let mut hasher = Sha256::new(); hasher.update(input); let result = hasher.finalize(); - result.to_vec() + result.into() } pub fn shared_secret_to_chacha( @@ -214,12 +231,12 @@ fn hash_to_g2( fn construct_tag_hash( commitment: E::G1Affine, - stream_ciphertext: &[u8], + ciphertext_hash: &[u8], aad: &[u8], ) -> Result { let mut hash_input = Vec::::new(); commitment.serialize_compressed(&mut hash_input)?; - hash_input.extend_from_slice(stream_ciphertext); + hash_input.extend_from_slice(ciphertext_hash); hash_input.extend_from_slice(aad); hash_to_g2(&hash_input) } @@ -251,7 +268,11 @@ mod tests { let plaintext = decrypt_symmetric(&ciphertext, aad, &privkey, g_inv).unwrap(); - assert_eq!(msg, plaintext) + assert_eq!(msg, plaintext); + + let bad: &[u8] = "bad-aad".as_bytes(); + + assert!(decrypt_symmetric(&ciphertext, bad, &privkey, g_inv).is_err()); } #[test] @@ -267,14 +288,14 @@ mod tests { encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); // So far, the ciphertext is valid - assert!(check_ciphertext_validity(&ciphertext, aad, &g_inv).is_ok()); + assert!(ciphertext.check(aad, &g_inv).is_ok()); // Malformed the ciphertext ciphertext.ciphertext[0] += 1; - assert!(check_ciphertext_validity(&ciphertext, aad, &g_inv).is_err()); + assert!(ciphertext.check(aad, &g_inv).is_err()); // Malformed the AAD let aad = "bad aad".as_bytes(); - assert!(check_ciphertext_validity(&ciphertext, aad, &g_inv).is_err()); + assert!(ciphertext.check(aad, &g_inv).is_err()); } } diff --git a/tpke/src/context.rs b/tpke/src/context.rs index 74f60c07..32fa91fb 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -3,9 +3,9 @@ use std::ops::Mul; use ark_ec::{pairing::Pairing, CurveGroup}; use crate::{ - check_ciphertext_validity, prepare_combine_simple, BlindedKeyShare, - Ciphertext, DecryptionShareFast, DecryptionSharePrecomputed, - DecryptionShareSimple, PrivateKeyShare, PublicKeyShare, Result, + prepare_combine_simple, BlindedKeyShare, Ciphertext, CiphertextHeader, + DecryptionShareFast, DecryptionSharePrecomputed, DecryptionShareSimple, + PrivateKeyShare, PublicKeyShare, Result, }; #[derive(Clone, Debug)] @@ -51,11 +51,7 @@ impl PrivateDecryptionContextFast { ciphertext: &Ciphertext, aad: &[u8], ) -> Result> { - check_ciphertext_validity::( - ciphertext, - aad, - &self.setup_params.g_inv, - )?; + ciphertext.check(aad, &self.setup_params.g_inv)?; let decryption_share = ciphertext .commitment @@ -82,13 +78,13 @@ pub struct PrivateDecryptionContextSimple { impl PrivateDecryptionContextSimple { pub fn create_share( &self, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], ) -> Result> { DecryptionShareSimple::create( &self.validator_private_key, &self.private_key_share, - ciphertext, + ciphertext_header, aad, &self.setup_params.g_inv, ) @@ -96,7 +92,7 @@ impl PrivateDecryptionContextSimple { pub fn create_share_precomputed( &self, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], ) -> Result> { let domain = self @@ -110,7 +106,7 @@ impl PrivateDecryptionContextSimple { self.index, &self.validator_private_key, &self.private_key_share, - ciphertext, + ciphertext_header, aad, &lagrange_coeffs[self.index], &self.setup_params.g_inv, diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index c3b85eb5..01ae5df7 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -9,7 +9,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::serde_as; use crate::{ - check_ciphertext_validity, generate_random, Ciphertext, PrivateKeyShare, + generate_random, Ciphertext, CiphertextHeader, PrivateKeyShare, PublicDecryptionContextFast, PublicDecryptionContextSimple, Result, }; @@ -31,10 +31,10 @@ pub struct ValidatorShareChecksum { impl ValidatorShareChecksum { pub fn new( validator_decryption_key: &E::ScalarField, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, ) -> Result { // C_i = dk_i^{-1} * U - let checksum = ciphertext + let checksum = ciphertext_header .commitment // TODO: Should we panic here? I think we should since that would mean that the decryption key is invalid. // And so, the validator should not be able to create a decryption share. @@ -90,15 +90,15 @@ impl DecryptionShareSimple { pub fn create( validator_decryption_key: &E::ScalarField, private_key_share: &PrivateKeyShare, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], g_inv: &E::G1Prepared, ) -> Result { - check_ciphertext_validity::(ciphertext, aad, g_inv)?; + ciphertext_header.check(aad, g_inv)?; Self::create_unchecked( validator_decryption_key, private_key_share, - ciphertext, + ciphertext_header, ) } @@ -107,17 +107,19 @@ impl DecryptionShareSimple { pub fn create_unchecked( validator_decryption_key: &E::ScalarField, private_key_share: &PrivateKeyShare, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, ) -> Result { // D_i = e(U, Z_i) let decryption_share = E::pairing( - ciphertext.commitment, + ciphertext_header.commitment, private_key_share.private_key_share, ) .0; - let validator_checksum = - ValidatorShareChecksum::new(validator_decryption_key, ciphertext)?; + let validator_checksum = ValidatorShareChecksum::new( + validator_decryption_key, + ciphertext_header, + )?; Ok(Self { decryption_share, @@ -160,17 +162,17 @@ impl DecryptionSharePrecomputed { validator_index: usize, validator_decryption_key: &E::ScalarField, private_key_share: &PrivateKeyShare, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, aad: &[u8], lagrange_coeff: &E::ScalarField, g_inv: &E::G1Prepared, ) -> Result { - check_ciphertext_validity::(ciphertext, aad, g_inv)?; + ciphertext_header.check(aad, g_inv)?; Self::create_unchecked( validator_index, validator_decryption_key, private_key_share, - ciphertext, + ciphertext_header, lagrange_coeff, ) } @@ -179,11 +181,12 @@ impl DecryptionSharePrecomputed { validator_index: usize, validator_decryption_key: &E::ScalarField, private_key_share: &PrivateKeyShare, - ciphertext: &Ciphertext, + ciphertext_header: &CiphertextHeader, lagrange_coeff: &E::ScalarField, ) -> Result { // U_{λ_i} = [λ_{i}(0)] U - let u_to_lagrange_coeff = ciphertext.commitment.mul(lagrange_coeff); + let u_to_lagrange_coeff = + ciphertext_header.commitment.mul(lagrange_coeff); // C_{λ_i} = e(U_{λ_i}, Z_i) let decryption_share = E::pairing( u_to_lagrange_coeff, @@ -191,8 +194,10 @@ impl DecryptionSharePrecomputed { ) .0; - let validator_checksum = - ValidatorShareChecksum::new(validator_decryption_key, ciphertext)?; + let validator_checksum = ValidatorShareChecksum::new( + validator_decryption_key, + ciphertext_header, + )?; Ok(Self { decrypter_index: validator_index, diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 651935ae..46a78f24 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -309,7 +309,9 @@ mod tests { ) -> SharedSecret { let decryption_shares: Vec<_> = contexts .iter() - .map(|c| c.create_share(ciphertext, aad).unwrap()) + .map(|c| { + c.create_share(&ciphertext.header().unwrap(), aad).unwrap() + }) .collect(); make_shared_secret( &contexts[0].public_decryption_contexts, @@ -459,7 +461,9 @@ mod tests { encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap(); let bad_aad = "bad aad".as_bytes(); - assert!(contexts[0].create_share(&ciphertext, bad_aad).is_err()); + assert!(contexts[0] + .create_share(&ciphertext.header().unwrap(), bad_aad) + .is_err()); } #[test] @@ -531,7 +535,9 @@ mod tests { // We need at least threshold shares to decrypt let decryption_shares: Vec<_> = contexts .iter() - .map(|c| c.create_share(&ciphertext, aad).unwrap()) + .map(|c| { + c.create_share(&ciphertext.header().unwrap(), aad).unwrap() + }) .take(threshold) .collect(); let pub_contexts = @@ -575,7 +581,12 @@ mod tests { let decryption_shares: Vec<_> = contexts .iter() .map(|context| { - context.create_share_precomputed(&ciphertext, aad).unwrap() + context + .create_share_precomputed( + &ciphertext.header().unwrap(), + aad, + ) + .unwrap() }) .collect(); @@ -620,7 +631,9 @@ mod tests { let decryption_shares: Vec<_> = contexts .iter() - .map(|c| c.create_share(&ciphertext, aad).unwrap()) + .map(|c| { + c.create_share(&ciphertext.header().unwrap(), aad).unwrap() + }) .collect(); // In simple tDec variant, we verify decryption shares only after decryption fails. @@ -772,7 +785,9 @@ mod tests { // Get decryption shares from remaining participants let mut decryption_shares: Vec<_> = remaining_participants .iter() - .map(|c| c.create_share(&ciphertext, aad).unwrap()) + .map(|c| { + c.create_share(&ciphertext.header().unwrap(), aad).unwrap() + }) .collect(); // Create a decryption share from a recovered private key share @@ -781,7 +796,7 @@ mod tests { DecryptionShareSimple::create( &new_validator_decryption_key, &new_private_key_share, - &ciphertext, + &ciphertext.header().unwrap(), aad, g_inv, ) @@ -845,7 +860,7 @@ mod tests { DecryptionShareSimple::create( &p.validator_private_key, &private_key_share, - &ciphertext, + &ciphertext.header().unwrap(), aad, g_inv, )