From 41e5be5dc0edc3c81e82d34f523108a7d06bd09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Fri, 28 Jul 2023 16:49:43 +0200 Subject: [PATCH 1/7] Refactor Ciphertext implementation. Fixes #144 * Remove unused Ciphertext.construct_tag_hash() * Refactor Ciphertext.check() to take the functionality of check_ciphertext_validity() function --- tpke/benches/tpke.rs | 3 +- tpke/src/ciphertext.rs | 73 ++++++++++++++++-------------------------- tpke/src/context.rs | 12 +++---- tpke/src/decryption.rs | 8 ++--- 4 files changed, 36 insertions(+), 60 deletions(-) diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 952c4947..89fef191 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -388,8 +388,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/ciphertext.rs b/tpke/src/ciphertext.rs index 814a72d2..fe6af03d 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -32,22 +32,32 @@ 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( + 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, aad) + let hash_g2 = E::G2Prepared::from(construct_tag_hash::( + self.commitment, + &self.ciphertext[..], + aad, + )?); + + let is_ciphertext_valid = E::multi_pairing( + // e(U, H_G2(U, sym_ctxt, aad)) = e(G, W) ==> + // e(U, H_G2(U, sym_ctxt, aad)) * e(G_inv, W) = 1 [self.commitment.into(), g_inv.to_owned()], [hash_g2, self.auth_tag.into()], ) - .0 == E::TargetField::one()) - } - - 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); + .0 == E::TargetField::one(); - hash_to_g2(&hash_input) + if is_ciphertext_valid { + Ok(true) + } else { + Err(Error::CiphertextVerificationFailed) + } } pub fn serialized_length(&self) -> usize { @@ -95,42 +105,13 @@ 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), @@ -161,7 +142,7 @@ pub fn decrypt_with_shared_secret( shared_secret: &SharedSecret, g_inv: &E::G1Prepared, ) -> Result> { - check_ciphertext_validity(ciphertext, aad, g_inv)?; + ciphertext.check(aad, g_inv)?; decrypt_with_shared_secret_unchecked(ciphertext, shared_secret) } @@ -267,14 +248,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..4a471925 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, 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 diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index c3b85eb5..ad319783 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -9,8 +9,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::serde_as; use crate::{ - check_ciphertext_validity, generate_random, Ciphertext, PrivateKeyShare, - PublicDecryptionContextFast, PublicDecryptionContextSimple, Result, + generate_random, Ciphertext, PrivateKeyShare, PublicDecryptionContextFast, + PublicDecryptionContextSimple, Result, }; #[serde_as] @@ -94,7 +94,7 @@ impl DecryptionShareSimple { aad: &[u8], g_inv: &E::G1Prepared, ) -> Result { - check_ciphertext_validity::(ciphertext, aad, g_inv)?; + ciphertext.check(aad, g_inv)?; Self::create_unchecked( validator_decryption_key, private_key_share, @@ -165,7 +165,7 @@ impl DecryptionSharePrecomputed { lagrange_coeff: &E::ScalarField, g_inv: &E::G1Prepared, ) -> Result { - check_ciphertext_validity::(ciphertext, aad, g_inv)?; + ciphertext.check(aad, g_inv)?; Self::create_unchecked( validator_index, validator_decryption_key, From 396b1d28297ea1a840c0b9dc4f6c6be061cd6bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Fri, 28 Jul 2023 17:15:44 +0200 Subject: [PATCH 2/7] Use symmetric ciphertext hash when creating/checking the auth_tag Closes #147 --- tpke/src/ciphertext.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index fe6af03d..490e0511 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -38,16 +38,17 @@ impl Ciphertext { // 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, aad) + // H_G2(U, sym_ctxt_digest, aad) + let ciphertext_hash = sha256(&self.ciphertext[..]); let hash_g2 = E::G2Prepared::from(construct_tag_hash::( self.commitment, - &self.ciphertext[..], + &ciphertext_hash, aad, )?); let is_ciphertext_valid = E::multi_pairing( - // e(U, H_G2(U, sym_ctxt, aad)) = e(G, W) ==> - // e(U, H_G2(U, sym_ctxt, aad)) * e(G_inv, W) = 1 + // 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()], ) @@ -92,8 +93,10 @@ pub fn encrypt( .encrypt(&nonce.0, message.as_secret().as_ref()) .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(); @@ -195,12 +198,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) } From e662e178d80f26b9a727b790e6d3e6f3456a6085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Mon, 31 Jul 2023 10:59:42 +0200 Subject: [PATCH 3/7] Use AEAD payloads (message + AAD) as input to chacha20poly1305. Fix #146 See https://docs.rs/aead/0.5.1/aead/struct.Payload.html --- tpke/src/ciphertext.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index 490e0511..a358ff93 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -4,7 +4,7 @@ use ark_ec::{pairing::Pairing, AffineRepr}; use ark_ff::{One, UniformRand}; use ark_serialize::{CanonicalSerialize, Compress}; use chacha20poly1305::{ - aead::{generic_array::GenericArray, Aead, KeyInit}, + aead::{generic_array::GenericArray, Aead, KeyInit, Payload}, ChaCha20Poly1305, }; use ferveo_common::serialization; @@ -89,8 +89,13 @@ 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); @@ -121,18 +126,22 @@ pub fn decrypt_symmetric( ) .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(); @@ -146,7 +155,7 @@ pub fn decrypt_with_shared_secret( g_inv: &E::G1Prepared, ) -> Result> { ciphertext.check(aad, g_inv)?; - decrypt_with_shared_secret_unchecked(ciphertext, shared_secret) + decrypt_with_shared_secret_unchecked(ciphertext, aad, shared_secret) } fn sha256(input: &[u8]) -> Vec { From e51e6ec6632720409c0fc9a525265c0ca1a0404b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 1 Aug 2023 11:47:21 +0200 Subject: [PATCH 4/7] Test for bad AAD input --- tpke/src/ciphertext.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index a358ff93..9e60199f 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -244,7 +244,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] From 0ec84b6b3bd6d583eef7fb928a9ffb5352d50bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 1 Aug 2023 11:50:43 +0200 Subject: [PATCH 5/7] Bump MSRV to 1.67.0 --- README.md | 2 +- rust-toolchain.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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"] From f427f0d12043f751905827ea31c7a179bd9a6180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 1 Aug 2023 13:40:44 +0200 Subject: [PATCH 6/7] Remove unused & incorrect ciphertext length method --- tpke/src/ciphertext.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index 9e60199f..f7137dbc 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -2,7 +2,7 @@ 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, Payload}, ChaCha20Poly1305, @@ -60,12 +60,6 @@ impl Ciphertext { Err(Error::CiphertextVerificationFailed) } } - - pub fn serialized_length(&self) -> usize { - self.commitment.serialized_size(Compress::No) - + self.auth_tag.serialized_size(Compress::No) - + self.ciphertext.len() - } } pub fn encrypt( From 4337c3c312719987405f620f2e377cf493ece6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=BA=C3=B1ez?= Date: Tue, 1 Aug 2023 14:06:16 +0200 Subject: [PATCH 7/7] Clippy stuff --- ferveo-common/src/lib.rs | 7 +++---- ferveo/src/bindings_python.rs | 9 +++------ ferveo/src/bindings_wasm.rs | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) 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/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 7d1cb93b..3cc88202 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) => { diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index 7b9ae484..b31866c4 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> { @@ -577,7 +577,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 {