Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamping Ferveo Ciphertexts (breaking changes ⚠️ ) #149

Merged
merged 7 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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},
cygnusv marked this conversation as resolved.
Show resolved Hide resolved
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
Loading