From 8f45430fb8b6198ae7895d8a598b9d0380f1e568 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 4 Jul 2023 15:58:20 +0200
Subject: [PATCH] precomputed variant fails for non-power-of-two number of
shares
---
ferveo/src/api.rs | 346 ++++++++++++++++++++++++----------------------
1 file changed, 179 insertions(+), 167 deletions(-)
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index a6ee4cd2..47e28a7d 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -374,179 +374,191 @@ mod test_ferveo_api {
fn test_server_api_tdec_precomputed() {
let rng = &mut StdRng::seed_from_u64(0);
- let tau = 1;
- let shares_num = 4;
- // In precomputed variant, the security threshold is equal to the number of shares
- // TODO: Refactor DKG constructor to not require security threshold or this case.
- // Or figure out a different way to simplify the precomputed variant API.
- let security_threshold = shares_num;
-
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
-
- // Now that every validator holds a dkg instance and a transcript for every other validator,
- // every validator can aggregate the transcripts
- let me = validators[0].clone();
- let mut dkg =
- Dkg::new(tau, shares_num, security_threshold, &validators, &me)
- .unwrap();
-
- let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
-
- // At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = dkg.public_key();
-
- // 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();
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext,
- aad,
- validator_keypair,
- )
- .unwrap()
- })
- .collect();
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is part of the client API
-
- let shared_secret = share_combine_precomputed(&decryption_shares);
- let plaintext = decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &SharedSecret(shared_secret),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
-
- // Since we're using a precomputed variant, we need all the shares to be able to decrypt
- // So if we remove one share, we should not be able to decrypt
- let decryption_shares =
- decryption_shares[..shares_num as usize - 1].to_vec();
-
- let shared_secret = share_combine_precomputed(&decryption_shares);
- let result = decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &SharedSecret(shared_secret),
- );
- assert!(result.is_err());
+ // Works for both power of 2 and non-power of 2
+ for shares_num in [4, 7] {
+ let tau = 1;
+ // In precomputed variant, the security threshold is equal to the number of shares
+ // TODO: Refactor DKG constructor to not require security threshold or this case.
+ // Or figure out a different way to simplify the precomputed variant API.
+ let security_threshold = shares_num;
+
+ let (messages, validators, validator_keypairs) =
+ make_test_inputs(rng, tau, security_threshold, shares_num);
+
+ // Now that every validator holds a dkg instance and a transcript for every other validator,
+ // every validator can aggregate the transcripts
+ let me = validators[0].clone();
+ let mut dkg =
+ Dkg::new(tau, shares_num, security_threshold, &validators, &me)
+ .unwrap();
+
+ let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // At this point, any given validator should be able to provide a DKG public key
+ let dkg_public_key = dkg.public_key();
+
+ // 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();
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ let decryption_shares: Vec<_> =
+ izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ tau,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated
+ .verify(shares_num, &messages)
+ .unwrap());
+ aggregate
+ .create_decryption_share_precomputed(
+ &dkg,
+ &ciphertext,
+ aad,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is part of the client API
+
+ let shared_secret = share_combine_precomputed(&decryption_shares);
+ let plaintext = decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &SharedSecret(shared_secret),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
+
+ // Since we're using a precomputed variant, we need all the shares to be able to decrypt
+ // So if we remove one share, we should not be able to decrypt
+ let decryption_shares =
+ decryption_shares[..shares_num as usize - 1].to_vec();
+
+ let shared_secret = share_combine_precomputed(&decryption_shares);
+ let result = decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &SharedSecret(shared_secret),
+ );
+ assert!(result.is_err());
+ }
}
#[test]
fn test_server_api_tdec_simple() {
let rng = &mut StdRng::seed_from_u64(0);
- let tau = 1;
- let shares_num = 4;
- let security_threshold = 3;
-
- let (messages, validators, validator_keypairs) =
- make_test_inputs(rng, tau, security_threshold, shares_num);
-
- // Now that every validator holds a dkg instance and a transcript for every other validator,
- // every validator can aggregate the transcripts
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- &validators[0],
- )
- .unwrap();
-
- let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
-
- // At this point, any given validator should be able to provide a DKG public key
- let public_key = dkg.public_key();
-
- // 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();
-
- // Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- tau,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(aggregate.verify(shares_num, &messages).unwrap());
- aggregate
- .create_decryption_share_simple(
- &dkg,
- &ciphertext,
- aad,
- validator_keypair,
- )
- .unwrap()
- })
- .collect();
-
- // Now, the decryption share can be used to decrypt the ciphertext
- // This part is part of the client API
-
- // In simple variant, we only need `security_threshold` shares to be able to decrypt
- let decryption_shares =
- decryption_shares[..security_threshold as usize].to_vec();
-
- let shared_secret = combine_shares_simple(&decryption_shares);
- let plaintext =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
- .unwrap();
- assert_eq!(plaintext, msg);
-
- // Let's say that we've only received `security_threshold - 1` shares
- // In this case, we should not be able to decrypt
- let decryption_shares =
- decryption_shares[..security_threshold as usize - 1].to_vec();
-
- let shared_secret = combine_shares_simple(&decryption_shares);
- let result =
- decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
- assert!(result.is_err());
+ // Works for both power of 2 and non-power of 2
+ for shares_num in [4, 7] {
+ let tau = 1;
+ let security_threshold = shares_num / 2 + 1;
+
+ let (messages, validators, validator_keypairs) =
+ make_test_inputs(rng, tau, security_threshold, shares_num);
+
+ // Now that every validator holds a dkg instance and a transcript for every other validator,
+ // every validator can aggregate the transcripts
+ let mut dkg = Dkg::new(
+ tau,
+ shares_num,
+ security_threshold,
+ &validators,
+ &validators[0],
+ )
+ .unwrap();
+
+ let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // At this point, any given validator should be able to provide a DKG public key
+ let public_key = dkg.public_key();
+
+ // 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();
+
+ // Having aggregated the transcripts, the validators can now create decryption shares
+ let decryption_shares: Vec<_> =
+ izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ tau,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(aggregate
+ .verify(shares_num, &messages)
+ .unwrap());
+ aggregate
+ .create_decryption_share_simple(
+ &dkg,
+ &ciphertext,
+ aad,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is part of the client API
+
+ // In simple variant, we only need `security_threshold` shares to be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let plaintext =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ .unwrap();
+ assert_eq!(plaintext, msg);
+
+ // Let's say that we've only received `security_threshold - 1` shares
+ // In this case, we should not be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize - 1].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let result =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
+ assert!(result.is_err());
+ }
}
#[test]