Skip to content

Commit

Permalink
Merge pull request #149 from cygnusv/thin
Browse files Browse the repository at this point in the history
Revamping Ferveo Ciphertexts (breaking changes ⚠️ )
  • Loading branch information
KPrasch authored Aug 22, 2023
2 parents 03b4e35 + 4337c3c commit f44e1be
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 92 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 3 additions & 4 deletions ferveo-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
}
}
}
Expand Down
9 changes: 3 additions & 6 deletions ferveo/src/bindings_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,12 @@ impl From<FerveoPythonError> 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 => {
Expand All @@ -90,8 +88,7 @@ impl From<FerveoPythonError> for PyErr {
}
Error::InvalidByteLength(expected, actual) => {
InvalidByteLength::new_err(format!(
"expected: {}, actual: {}",
expected, actual
"expected: {expected}, actual: {actual}"
))
}
Error::InvalidVariant(variant) => {
Expand Down
4 changes: 2 additions & 2 deletions ferveo/src/bindings_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn set_panic_hook() {
}

pub fn map_js_err<T: fmt::Display>(err: T) -> Error {
Error::new(&format!("{}", err))
Error::new(&format!("{err}"))
}

pub fn to_js_bytes<T: ToBytes>(t: &T) -> Result<Vec<u8>, Error> {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -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"]
3 changes: 1 addition & 2 deletions tpke/benches/tpke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
))
Expand Down
119 changes: 55 additions & 64 deletions tpke/src/ciphertext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -32,28 +32,33 @@ pub struct Ciphertext<E: Pairing> {
}

impl<E: Pairing> Ciphertext<E> {
pub fn check(&self, g_inv: &E::G1Prepared) -> Result<bool> {
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<bool> {
// 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 ciphertext_hash = sha256(&self.ciphertext[..]);
let hash_g2 = E::G2Prepared::from(construct_tag_hash::<E>(
self.commitment,
&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())
}

fn construct_tag_hash(&self) -> Result<E::G2Affine> {
let mut hash_input = Vec::<u8>::new();
self.commitment.serialize_compressed(&mut hash_input)?;
hash_input.extend_from_slice(&self.ciphertext);
.0 == E::TargetField::one();

hash_to_g2(&hash_input)
}

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)
}
}
}

Expand All @@ -78,12 +83,19 @@ pub fn encrypt<E: Pairing>(

let nonce = Nonce::from_commitment::<E>(commitment)?;
let shared_secret = SharedSecret::<E>(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::<E>(commitment, &ciphertext, aad)?
let auth_tag = construct_tag_hash::<E>(commitment, &ciphertext_hash, aad)?
.mul(rand_element)
.into();

Expand All @@ -95,60 +107,35 @@ pub fn encrypt<E: Pairing>(
})
}

/// 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<E: Pairing>(
c: &Ciphertext<E>,
aad: &[u8],
g_inv: &E::G1Prepared,
) -> Result<()> {
// H_G2(U, aad)
let hash_g2 = E::G2Prepared::from(construct_tag_hash::<E>(
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<E: Pairing>(
ciphertext: &Ciphertext<E>,
aad: &[u8],
private_key: &E::G2Affine,
g_inv: &E::G1Prepared,
) -> Result<Vec<u8>> {
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<E: Pairing>(
ciphertext: &Ciphertext<E>,
aad: &[u8],
shared_secret: &SharedSecret<E>,
) -> Result<Vec<u8>> {
let nonce = Nonce::from_commitment::<E>(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();

Expand All @@ -161,8 +148,8 @@ pub fn decrypt_with_shared_secret<E: Pairing>(
shared_secret: &SharedSecret<E>,
g_inv: &E::G1Prepared,
) -> Result<Vec<u8>> {
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<u8> {
Expand Down Expand Up @@ -214,12 +201,12 @@ fn hash_to_g2<T: ark_serialize::CanonicalDeserialize>(

fn construct_tag_hash<E: Pairing>(
commitment: E::G1Affine,
stream_ciphertext: &[u8],
ciphertext_hash: &[u8],
aad: &[u8],
) -> Result<E::G2Affine> {
let mut hash_input = Vec::<u8>::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)
}
Expand Down Expand Up @@ -251,7 +238,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]
Expand All @@ -267,14 +258,14 @@ mod tests {
encrypt::<E>(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());
}
}
12 changes: 4 additions & 8 deletions tpke/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -51,11 +51,7 @@ impl<E: Pairing> PrivateDecryptionContextFast<E> {
ciphertext: &Ciphertext<E>,
aad: &[u8],
) -> Result<DecryptionShareFast<E>> {
check_ciphertext_validity::<E>(
ciphertext,
aad,
&self.setup_params.g_inv,
)?;
ciphertext.check(aad, &self.setup_params.g_inv)?;

let decryption_share = ciphertext
.commitment
Expand Down
8 changes: 4 additions & 4 deletions tpke/src/decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -94,7 +94,7 @@ impl<E: Pairing> DecryptionShareSimple<E> {
aad: &[u8],
g_inv: &E::G1Prepared,
) -> Result<Self> {
check_ciphertext_validity::<E>(ciphertext, aad, g_inv)?;
ciphertext.check(aad, g_inv)?;
Self::create_unchecked(
validator_decryption_key,
private_key_share,
Expand Down Expand Up @@ -165,7 +165,7 @@ impl<E: Pairing> DecryptionSharePrecomputed<E> {
lagrange_coeff: &E::ScalarField,
g_inv: &E::G1Prepared,
) -> Result<Self> {
check_ciphertext_validity::<E>(ciphertext, aad, g_inv)?;
ciphertext.check(aad, g_inv)?;
Self::create_unchecked(
validator_index,
validator_decryption_key,
Expand Down

0 comments on commit f44e1be

Please sign in to comment.