Skip to content

Commit

Permalink
fix: not using subset of participants in precomputed variant
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Mar 13, 2024
1 parent a4ebfab commit 0e27a90
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 147 deletions.
35 changes: 18 additions & 17 deletions ferveo-python/test/test_ferveo.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@ def combine_shares_for_variant(v: FerveoVariant, decryption_shares):


def scenario_for_variant(
variant: FerveoVariant, shares_num, validators_num, threshold, dec_shares_to_use
variant: FerveoVariant,
shares_num,
validators_num,
threshold,
dec_shares_to_use
):
if variant not in [FerveoVariant.Simple, FerveoVariant.Precomputed]:
raise ValueError("Unknown variant: " + variant)

if validators_num < shares_num:
raise ValueError("validators_num must be >= shares_num")

# TODO: Validate that
# if variant == FerveoVariant.Precomputed and dec_shares_to_use != validators_num:
# raise ValueError(
# "In precomputed variant, dec_shares_to_use must be equal to validators_num"
# )
if shares_num < threshold:
raise ValueError("shares_num must be >= threshold")

tau = 1
validator_keypairs = [Keypair.random() for _ in range(0, validators_num)]
Expand Down Expand Up @@ -90,6 +91,8 @@ def scenario_for_variant(
client_aggregate = AggregatedTranscript(messages)
assert client_aggregate.verify(validators_num, messages)

# At this point, DKG is done and we are proceeding to threshold decryption

# Client creates a ciphertext and requests decryption shares from validators
msg = "abc".encode()
aad = "my-aad".encode()
Expand Down Expand Up @@ -122,12 +125,7 @@ def scenario_for_variant(
# Client combines the decryption shares and decrypts the ciphertext
shared_secret = combine_shares_for_variant(variant, decryption_shares)

if variant == FerveoVariant.Simple and len(decryption_shares) < threshold:
with pytest.raises(ThresholdEncryptionError):
decrypt_with_shared_secret(ciphertext, aad, shared_secret)
return

if variant == FerveoVariant.Precomputed and len(decryption_shares) < threshold:
if len(decryption_shares) < threshold:
with pytest.raises(ThresholdEncryptionError):
decrypt_with_shared_secret(ciphertext, aad, shared_secret)
return
Expand All @@ -152,39 +150,42 @@ def test_simple_tdec_has_enough_messages():
def test_simple_tdec_doesnt_have_enough_messages():
shares_num = 4
threshold = shares_num - 1
dec_shares_to_use = threshold - 1
for validators_num in [shares_num, shares_num + 2]:
scenario_for_variant(
FerveoVariant.Simple,
shares_num=shares_num,
validators_num=validators_num,
threshold=threshold,
dec_shares_to_use=validators_num - 1,
dec_shares_to_use=dec_shares_to_use,
)


def test_precomputed_tdec_has_enough_messages():
shares_num = 4
threshold = shares_num # in precomputed variant, we need all shares
threshold = shares_num - 1
dec_shares_to_use = threshold
for validators_num in [shares_num, shares_num + 2]:
scenario_for_variant(
FerveoVariant.Precomputed,
shares_num=shares_num,
validators_num=validators_num,
threshold=threshold,
dec_shares_to_use=validators_num,
dec_shares_to_use=dec_shares_to_use,
)


def test_precomputed_tdec_doesnt_have_enough_messages():
shares_num = 4
threshold = shares_num # in precomputed variant, we need all shares
threshold = shares_num - 1
dec_shares_to_use = threshold - 1
for validators_num in [shares_num, shares_num + 2]:
scenario_for_variant(
FerveoVariant.Simple,
shares_num=shares_num,
validators_num=validators_num,
threshold=threshold,
dec_shares_to_use=threshold - 1,
dec_shares_to_use=dec_shares_to_use,
)


Expand Down
9 changes: 8 additions & 1 deletion ferveo-tdec/benches/tpke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl SetupSimple {
let aad: &[u8] = "my-aad".as_bytes();

let (pubkey, privkey, contexts) =
setup_simple::<E>(threshold, shares_num, rng);
setup_simple::<E>(shares_num, threshold, rng);

// Ciphertext.commitment is already computed to match U
let ciphertext =
Expand Down Expand Up @@ -200,6 +200,9 @@ pub fn bench_create_decryption_share(c: &mut Criterion) {
};
let simple_precomputed = {
let setup = SetupSimple::new(shares_num, MSG_SIZE_CASES[0], rng);
// TODO: Use threshold instead of shares_num
let selected_participants = (0..shares_num).collect::<Vec<_>>();

move || {
black_box(
setup
Expand All @@ -209,6 +212,7 @@ pub fn bench_create_decryption_share(c: &mut Criterion) {
context.create_share_precomputed(
&setup.shared.ciphertext.header().unwrap(),
&setup.shared.aad,
&selected_participants,
)
})
.collect::<Vec<_>>(),
Expand Down Expand Up @@ -295,6 +299,8 @@ pub fn bench_share_combine(c: &mut Criterion) {
};
let simple_precomputed = {
let setup = SetupSimple::new(shares_num, MSG_SIZE_CASES[0], rng);
// TODO: Use threshold instead of shares_num
let selected_participants = (0..shares_num).collect::<Vec<_>>();

let decryption_shares: Vec<_> = setup
.contexts
Expand All @@ -304,6 +310,7 @@ pub fn bench_share_combine(c: &mut Criterion) {
.create_share_precomputed(
&setup.shared.ciphertext.header().unwrap(),
&setup.shared.aad,
&selected_participants,
)
.unwrap()
})
Expand Down
9 changes: 5 additions & 4 deletions ferveo-tdec/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,14 @@ impl<E: Pairing> PrivateDecryptionContextSimple<E> {
&self,
ciphertext_header: &CiphertextHeader<E>,
aad: &[u8],
selected_participants: &[usize],
) -> Result<DecryptionSharePrecomputed<E>> {
let domain = self
.public_decryption_contexts
let selected_domain_points = selected_participants
.iter()
.map(|c| c.domain)
.map(|i| self.public_decryption_contexts[*i].domain)
.collect::<Vec<_>>();
let lagrange_coeffs = prepare_combine_simple::<E>(&domain);
let lagrange_coeffs =
prepare_combine_simple::<E>(&selected_domain_points);

DecryptionSharePrecomputed::create(
self.index,
Expand Down
78 changes: 47 additions & 31 deletions ferveo-tdec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ pub mod test_common {
}

pub fn setup_simple<E: Pairing>(
threshold: usize,
shares_num: usize,
threshold: usize,
rng: &mut impl rand::Rng,
) -> (
PublicKey<E>,
Expand Down Expand Up @@ -264,17 +264,17 @@ pub mod test_common {

pub fn setup_precomputed<E: Pairing>(
shares_num: usize,
threshold: usize,
rng: &mut impl rand::Rng,
) -> (
PublicKey<E>,
PrivateKeyShare<E>,
Vec<PrivateDecryptionContextSimple<E>>,
) {
// In precomputed variant, the security threshold is equal to the number of shares
setup_simple::<E>(shares_num, shares_num, rng)
setup_simple::<E>(shares_num, threshold, rng)
}

pub fn create_shared_secret<E: Pairing>(
pub fn create_shared_secret_simple<E: Pairing>(
pub_contexts: &[PublicDecryptionContextSimple<E>],
decryption_shares: &[DecryptionShareSimple<E>],
) -> SharedSecret<E> {
Expand All @@ -291,8 +291,12 @@ mod tests {
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
use ark_std::{test_rng, UniformRand};
use ferveo_common::{FromBytes, ToBytes};
use rand::seq::IteratorRandom;

use crate::test_common::{create_shared_secret, setup_simple, *};
use crate::{
api::DecryptionSharePrecomputed,
test_common::{create_shared_secret_simple, setup_simple, *},
};

type E = ark_bls12_381::Bls12_381;
type TargetField = <E as Pairing>::TargetField;
Expand Down Expand Up @@ -378,7 +382,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();

let (pubkey, _, contexts) =
setup_simple::<E>(threshold, shares_num, rng);
setup_simple::<E>(shares_num, threshold, rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();

Expand Down Expand Up @@ -447,7 +451,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();

let (pubkey, _, contexts) =
setup_simple::<E>(threshold, shares_num, &mut rng);
setup_simple::<E>(shares_num, threshold, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;

let ciphertext =
Expand All @@ -462,10 +466,10 @@ mod tests {
})
.take(threshold)
.collect();
let pub_contexts =
let selected_contexts =
contexts[0].public_decryption_contexts[..threshold].to_vec();
let shared_secret =
create_shared_secret(&pub_contexts, &decryption_shares);
create_shared_secret_simple(&selected_contexts, &decryption_shares);

test_ciphertext_validation_fails(
&msg,
Expand All @@ -476,44 +480,58 @@ mod tests {
);

// If we use less than threshold shares, we should fail
let decryption_shares = decryption_shares[..threshold - 1].to_vec();
let pub_contexts = pub_contexts[..threshold - 1].to_vec();
let shared_secret =
create_shared_secret(&pub_contexts, &decryption_shares);

let result =
decrypt_with_shared_secret(&ciphertext, aad, &shared_secret, g_inv);
let not_enough_dec_shares = decryption_shares[..threshold - 1].to_vec();
let not_enough_contexts = selected_contexts[..threshold - 1].to_vec();
let bash_shared_secret = create_shared_secret_simple(
&not_enough_contexts,
&not_enough_dec_shares,
);
let result = decrypt_with_shared_secret(
&ciphertext,
aad,
&bash_shared_secret,
g_inv,
);
assert!(result.is_err());
}

#[test]
fn tdec_precomputed_variant_e2e() {
let mut rng = &mut test_rng();
let shares_num = 16;
let threshold = shares_num * 2 / 3;
let msg = "my-msg".as_bytes().to_vec();
let aad: &[u8] = "my-aad".as_bytes();

let (pubkey, _, contexts) =
setup_precomputed::<E>(shares_num, &mut rng);
setup_precomputed::<E>(shares_num, threshold, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
.unwrap();

let decryption_shares: Vec<_> = contexts
let selected_participants =
(0..threshold).choose_multiple(rng, threshold);
let selected_contexts = contexts
.iter()
.filter(|c| selected_participants.contains(&c.index))
.cloned()
.collect::<Vec<_>>();

let decryption_shares = selected_contexts
.iter()
.map(|context| {
context
.create_share_precomputed(
&ciphertext.header().unwrap(),
aad,
&selected_participants,
)
.unwrap()
})
.collect();
.collect::<Vec<DecryptionSharePrecomputed>>();

let shared_secret = share_combine_precomputed::<E>(&decryption_shares);

test_ciphertext_validation_fails(
&msg,
aad,
Expand All @@ -522,19 +540,17 @@ mod tests {
g_inv,
);

// Note that in this variant, if we use less than `share_num` shares, we will get a
// decryption error.

let not_enough_shares = &decryption_shares[0..shares_num - 1];
let bad_shared_secret =
share_combine_precomputed::<E>(not_enough_shares);
assert!(decrypt_with_shared_secret(
// If we use less than threshold shares, we should fail
let not_enough_dec_shares = decryption_shares[..threshold - 1].to_vec();
let bash_shared_secret =
share_combine_precomputed(&not_enough_dec_shares);
let result = decrypt_with_shared_secret(
&ciphertext,
aad,
&bad_shared_secret,
&bash_shared_secret,
g_inv,
)
.is_err());
);
assert!(result.is_err());
}

#[test]
Expand All @@ -546,7 +562,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();

let (pubkey, _, contexts) =
setup_simple::<E>(threshold, shares_num, &mut rng);
setup_simple::<E>(shares_num, threshold, &mut rng);

let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
Expand Down
3 changes: 3 additions & 0 deletions ferveo-wasm/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ fn tdec_precomputed() {
ciphertext,
) = setup_dkg(shares_num, validators_num, security_threshold);

// TODO: Adjust the subset of validators used by the client

// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares = zip_eq(validators, validator_keypairs)
.map(|(validator, keypair)| {
Expand All @@ -189,6 +191,7 @@ fn tdec_precomputed() {
&ciphertext.header().unwrap(),
&aad,
&keypair,
&validators_js,
)
.unwrap()
})
Expand Down
Loading

0 comments on commit 0e27a90

Please sign in to comment.