diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index cb125135..fdf8a34d 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -83,6 +83,7 @@ mod test_dkg_full { let plaintext = tpke::checked_decrypt_with_shared_secret( &ciphertext, aad, + &dkg.pvss_params.g_inv(), &shared_secret, ) .unwrap(); @@ -130,6 +131,7 @@ mod test_dkg_full { let plaintext = tpke::checked_decrypt_with_shared_secret( &ciphertext, aad, + &dkg.pvss_params.g_inv(), &shared_secret, ) .unwrap(); diff --git a/ferveo/src/vss/pvss.rs b/ferveo/src/vss/pvss.rs index 995dacf9..319ef9a0 100644 --- a/ferveo/src/vss/pvss.rs +++ b/ferveo/src/vss/pvss.rs @@ -41,6 +41,12 @@ pub struct PubliclyVerifiableParams { pub h: E::G2Projective, } +impl PubliclyVerifiableParams { + pub fn g_inv(&self) -> E::G1Prepared { + E::G1Prepared::from(-self.g.into_affine()) + } +} + /// Each validator posts a transcript to the chain. Once enough /// validators have done this (their total voting power exceeds /// 2/3 the total), this will be aggregated into a final key @@ -156,8 +162,6 @@ impl PubliclyVerifiableSS { let a_i = commitment[validator.share_index]; // We verify that e(G, Y_i) = e(A_i, ek_i) for validator i // See #4 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf - // Y = \sum_i y_i \alpha^i - // A = \sum_i a_i \alpha^i // e(G,Y) = e(A, ek) E::pairing(dkg.pvss_params.g, *y_i) == E::pairing(a_i, ek_i) }) @@ -338,7 +342,7 @@ mod test_pvss { assert!(!pvss.verify_optimistic()); } - /// Check that if PVSS shares are tempered with, the full verification fails + /// Check that if PVSS shares are tampered with, the full verification fails #[test] fn test_verify_pvss_bad_shares() { let rng = &mut ark_std::test_rng(); diff --git a/tpke-wasm/src/lib.rs b/tpke-wasm/src/lib.rs index 69aa475c..9717d9d0 100644 --- a/tpke-wasm/src/lib.rs +++ b/tpke-wasm/src/lib.rs @@ -2,6 +2,8 @@ mod utils; extern crate group_threshold_cryptography as tpke; +use ark_bls12_381::G1Affine; +use ark_ec::AffineCurve; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -18,6 +20,8 @@ pub type TpkeDecryptionShare = tpke::DecryptionShareFast; pub type TpkePublicDecryptionContext = tpke::PublicDecryptionContextFast; pub type TpkeSharedSecret = ::Fqk; +pub type TpkeG1Prepared = + ::G1Prepared; #[wasm_bindgen] #[derive(Clone, Debug)] @@ -222,6 +226,7 @@ impl Setup { pub struct Ciphertext { pub(crate) ciphertext: TpkeCiphertext, pub(crate) aad: Vec, + pub(crate) g_inv: TpkeG1Prepared, } #[wasm_bindgen] @@ -233,11 +238,15 @@ pub fn encrypt( set_panic_hook(); let mut rng = rand::thread_rng(); + // TODO: Expose `TpkeG1Prepared` to WASM and use it here + let g_inv = TpkeG1Prepared::from(-G1Affine::prime_subgroup_generator()); let ciphertext = tpke::encrypt::<_, E>(message, aad, &public_key.0, &mut rng); + Ciphertext { ciphertext, aad: aad.to_vec(), + g_inv, } } @@ -248,7 +257,8 @@ pub fn decrypt(ciphertext: &Ciphertext, private_key: &PrivateKey) -> Vec { tpke::checked_decrypt( &ciphertext.ciphertext, &ciphertext.aad, - private_key.0, + &ciphertext.g_inv, + &private_key.0, ) .unwrap() } @@ -311,6 +321,7 @@ pub fn decrypt_with_shared_secret( tpke::checked_decrypt_with_shared_secret( &ciphertext.ciphertext, &ciphertext.aad, + &ciphertext.g_inv, &shared_secret.0, ) .unwrap() diff --git a/tpke/benches/tpke.rs b/tpke/benches/tpke.rs index 62c5bc7e..9bfd6f90 100644 --- a/tpke/benches/tpke.rs +++ b/tpke/benches/tpke.rs @@ -50,8 +50,15 @@ impl SetupFast { let mut decryption_shares: Vec> = vec![]; for context in contexts.iter() { - decryption_shares - .push(context.create_share(&ciphertext, aad).unwrap()); + decryption_shares.push( + context + .create_share( + &ciphertext, + aad, + &contexts[0].setup_params.g_inv, + ) + .unwrap(), + ); } let pub_contexts = contexts[0].clone().public_decryption_contexts; @@ -105,7 +112,15 @@ 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, + aad, + &contexts[0].setup_params.g_inv, + ) + .unwrap() + }) .collect(); let pub_contexts = contexts[0].clone().public_decryption_contexts; @@ -157,6 +172,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) { ctx.create_share( &setup.shared.ciphertext, &setup.shared.aad, + &setup.contexts[0].setup_params.g_inv, ) }) .collect::>() @@ -176,6 +192,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) { ctx.create_share( &setup.shared.ciphertext, &setup.shared.aad, + &setup.contexts[0].setup_params.g_inv, ) }) .collect::>() @@ -342,6 +359,7 @@ pub fn bench_share_encrypt_decrypt(c: &mut Criterion) { checked_decrypt_with_shared_secret::( &setup.shared.ciphertext, &setup.shared.aad, + &setup.contexts[0].setup_params.g_inv, &setup.shared.shared_secret, ) .unwrap(), @@ -373,6 +391,7 @@ pub fn bench_validity_checks(c: &mut Criterion) { black_box(check_ciphertext_validity( &setup.shared.ciphertext, &setup.shared.aad, + &setup.contexts[0].setup_params.g_inv, )) .unwrap(); } diff --git a/tpke/src/ciphertext.rs b/tpke/src/ciphertext.rs index 4b94238d..da57af4d 100644 --- a/tpke/src/ciphertext.rs +++ b/tpke/src/ciphertext.rs @@ -106,8 +106,8 @@ pub fn encrypt( pub fn check_ciphertext_validity( c: &Ciphertext, aad: &[u8], + g_inv: &E::G1Prepared, ) -> Result<()> { - let g_inv = E::G1Prepared::from(-E::G1Affine::prime_subgroup_generator()); // H_G2(U, aad) let hash_g2 = E::G2Prepared::from(construct_tag_hash::( c.commitment, @@ -118,7 +118,7 @@ pub fn check_ciphertext_validity( let is_ciphertext_valid = E::product_of_pairings(&[ // e(U, H_G2(U, aad)) = e(G, W) (E::G1Prepared::from(c.commitment), hash_g2), - (g_inv, E::G2Prepared::from(c.auth_tag)), + (g_inv.clone(), E::G2Prepared::from(c.auth_tag)), ]) == E::Fqk::one(); if is_ciphertext_valid { @@ -131,12 +131,13 @@ pub fn check_ciphertext_validity( pub fn checked_decrypt( ciphertext: &Ciphertext, aad: &[u8], - privkey: E::G2Affine, + g_inv: &E::G1Prepared, + privkey: &E::G2Affine, ) -> Result> { - check_ciphertext_validity(ciphertext, aad)?; + check_ciphertext_validity(ciphertext, aad, g_inv)?; let s = E::product_of_pairings(&[( E::G1Prepared::from(ciphertext.commitment), - E::G2Prepared::from(privkey), + E::G2Prepared::from(*privkey), )]); Ok(decrypt_with_shared_secret(ciphertext, &s)) } @@ -157,9 +158,10 @@ fn decrypt_with_shared_secret( pub fn checked_decrypt_with_shared_secret( ciphertext: &Ciphertext, aad: &[u8], + g_inv: &E::G1Prepared, s: &E::Fqk, ) -> Result> { - check_ciphertext_validity(ciphertext, aad)?; + check_ciphertext_validity(ciphertext, aad, g_inv)?; Ok(decrypt_with_shared_secret(ciphertext, s)) } diff --git a/tpke/src/context.rs b/tpke/src/context.rs index 4b3452bd..9a8e8fc3 100644 --- a/tpke/src/context.rs +++ b/tpke/src/context.rs @@ -41,8 +41,9 @@ impl PrivateDecryptionContextFast { &self, ciphertext: &Ciphertext, aad: &[u8], + g_inv: &E::G1Prepared, ) -> Result> { - check_ciphertext_validity::(ciphertext, aad)?; + check_ciphertext_validity::(ciphertext, aad, g_inv)?; let decryption_share = ciphertext .commitment @@ -70,8 +71,9 @@ impl PrivateDecryptionContextSimple { &self, ciphertext: &Ciphertext, aad: &[u8], + g_inv: &E::G1Prepared, ) -> Result> { - check_ciphertext_validity::(ciphertext, aad)?; + check_ciphertext_validity::(ciphertext, aad, g_inv)?; let u = ciphertext.commitment; let z_i = self.private_key_share.private_key_share; diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 69243a9e..ea260a0a 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -269,6 +269,7 @@ pub fn setup_simple( mod tests { use crate::*; use ark_bls12_381::Fr; + use ark_ec::ProjectiveCurve; use ark_std::test_rng; use itertools::Itertools; @@ -302,11 +303,14 @@ mod tests { let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, privkey, _) = setup_fast::(threshold, shares_num, rng); + let (pubkey, privkey, contexts) = + setup_fast::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::(msg, aad, &pubkey, rng); - let plaintext = checked_decrypt(&ciphertext, aad, privkey).unwrap(); + let plaintext = + checked_decrypt(&ciphertext, aad, g_inv, &privkey).unwrap(); assert_eq!(msg, plaintext) } @@ -316,11 +320,16 @@ mod tests { aad: &[u8], ciphertext: &Ciphertext, shared_secret: &E::Fqk, + g_inv: &E::G1Prepared, ) { // So far, the ciphertext is valid - let plaintext = - checked_decrypt_with_shared_secret(ciphertext, aad, shared_secret) - .unwrap(); + let plaintext = checked_decrypt_with_shared_secret( + ciphertext, + aad, + g_inv, + shared_secret, + ) + .unwrap(); assert_eq!(plaintext, msg); // Malformed the ciphertext @@ -329,6 +338,7 @@ mod tests { assert!(checked_decrypt_with_shared_secret( &ciphertext, aad, + g_inv, shared_secret, ) .is_err()); @@ -338,6 +348,7 @@ mod tests { assert!(checked_decrypt_with_shared_secret( &ciphertext, aad, + g_inv, shared_secret, ) .is_err()); @@ -351,19 +362,20 @@ mod tests { let msg: &[u8] = "abc".as_bytes(); let aad: &[u8] = "my-aad".as_bytes(); - let (pubkey, _, _) = setup_fast::(threshold, shares_num, rng); + let (pubkey, _, contexts) = setup_fast::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let mut ciphertext = encrypt::(msg, aad, &pubkey, rng); // So far, the ciphertext is valid - assert!(check_ciphertext_validity(&ciphertext, aad).is_ok()); + assert!(check_ciphertext_validity(&ciphertext, aad, g_inv).is_ok()); // Malformed the ciphertext ciphertext.ciphertext[0] += 1; - assert!(check_ciphertext_validity(&ciphertext, aad).is_err()); + assert!(check_ciphertext_validity(&ciphertext, aad, g_inv).is_err()); // Malformed the AAD let aad = "bad aad".as_bytes(); - assert!(check_ciphertext_validity(&ciphertext, aad).is_err()); + assert!(check_ciphertext_validity(&ciphertext, aad, g_inv).is_err()); } #[test] @@ -375,10 +387,13 @@ mod tests { let aad: &[u8] = "my-aad".as_bytes(); let (pubkey, _, contexts) = setup_fast::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::(msg, aad, &pubkey, rng); let bad_aad = "bad aad".as_bytes(); - assert!(contexts[0].create_share(&ciphertext, bad_aad).is_err()); + assert!(contexts[0] + .create_share(&ciphertext, bad_aad, g_inv) + .is_err()); } #[test] @@ -391,10 +406,13 @@ mod tests { let (pubkey, _, contexts) = setup_simple::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::(msg, aad, &pubkey, rng); let bad_aad = "bad aad".as_bytes(); - assert!(contexts[0].create_share(&ciphertext, bad_aad).is_err()); + assert!(contexts[0] + .create_share(&ciphertext, bad_aad, g_inv) + .is_err()); } #[test] @@ -407,12 +425,13 @@ mod tests { let (pubkey, _, contexts) = setup_fast::(threshold, shares_num, &mut rng); + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); let mut decryption_shares: Vec> = vec![]; for context in contexts.iter() { decryption_shares - .push(context.create_share(&ciphertext, aad).unwrap()); + .push(context.create_share(&ciphertext, aad, g_inv).unwrap()); } // TODO: Verify and enable this check @@ -435,7 +454,13 @@ mod tests { ) .unwrap(); - test_ciphertext_validation_fails(msg, aad, &ciphertext, &shared_secret); + test_ciphertext_validation_fails( + msg, + aad, + &ciphertext, + &shared_secret, + g_inv, + ); } #[test] @@ -448,14 +473,14 @@ mod tests { let (pubkey, _, contexts) = setup_simple::(threshold, shares_num, &mut rng); + let g_inv = &contexts[0].setup_params.g_inv; - // Ciphertext.commitment is already computed to match U let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); // Create decryption shares let decryption_shares: Vec<_> = contexts .iter() - .map(|c| c.create_share(&ciphertext, aad).unwrap()) + .map(|c| c.create_share(&ciphertext, aad, g_inv).unwrap()) .collect(); let domain = contexts[0] @@ -468,7 +493,13 @@ mod tests { let shared_secret = share_combine_simple::(&decryption_shares, &lagrange); - test_ciphertext_validation_fails(msg, aad, &ciphertext, &shared_secret); + test_ciphertext_validation_fails( + msg, + aad, + &ciphertext, + &shared_secret, + g_inv, + ); } #[test] @@ -481,7 +512,7 @@ mod tests { let (pubkey, _, contexts) = setup_simple::(threshold, shares_num, &mut rng); - + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); let domain = contexts[0] @@ -502,7 +533,13 @@ mod tests { let shared_secret = share_combine_simple_precomputed::(&decryption_shares); - test_ciphertext_validation_fails(msg, aad, &ciphertext, &shared_secret); + test_ciphertext_validation_fails( + msg, + aad, + &ciphertext, + &shared_secret, + g_inv, + ); } #[test] @@ -548,10 +585,11 @@ mod tests { contexts: &[PrivateDecryptionContextSimple], ciphertext: &Ciphertext, aad: &[u8], + g_inv: &E::G1Prepared, ) -> E::Fqk { let decryption_shares: Vec<_> = contexts .iter() - .map(|c| c.create_share(ciphertext, aad).unwrap()) + .map(|c| c.create_share(ciphertext, aad, g_inv).unwrap()) .collect(); make_shared_secret( &contexts[0].public_decryption_contexts, @@ -590,16 +628,21 @@ mod tests { let (pubkey, _, contexts) = setup_simple::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); // Create an initial shared secret - let old_shared_secret = - make_shared_secret_from_contexts(&contexts, &ciphertext, aad); + let old_shared_secret = make_shared_secret_from_contexts( + &contexts, + &ciphertext, + aad, + g_inv, + ); // Now, we're going to recover a new share at a random point and check that the shared secret is still the same // Remove one participant from the contexts and all nested structures - let mut remaining_participants = contexts; + let mut remaining_participants = contexts.clone(); let removed_participant = remaining_participants.pop().unwrap(); for p in &mut remaining_participants { p.public_decryption_contexts.pop().unwrap(); @@ -620,7 +663,7 @@ mod tests { // Creating decryption shares let mut decryption_shares: Vec<_> = remaining_participants .iter() - .map(|c| c.create_share(&ciphertext, aad).unwrap()) + .map(|c| c.create_share(&ciphertext, aad, g_inv).unwrap()) .collect(); decryption_shares.push(DecryptionShareSimple { decrypter_index: removed_participant.index, @@ -652,12 +695,17 @@ mod tests { let (pubkey, _, contexts) = setup_simple::(threshold, shares_num, rng); + let g_inv = &contexts[0].setup_params.g_inv; let pub_contexts = contexts[0].public_decryption_contexts.clone(); let ciphertext = encrypt::<_, E>(msg, aad, &pubkey, rng); // Create an initial shared secret - let old_shared_secret = - make_shared_secret_from_contexts(&contexts, &ciphertext, aad); + let old_shared_secret = make_shared_secret_from_contexts( + &contexts, + &ciphertext, + aad, + g_inv, + ); // Now, we're going to refresh the shares and check that the shared secret is the same