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 14, 2024
1 parent a4ebfab commit 18487e6
Show file tree
Hide file tree
Showing 16 changed files with 390 additions and 280 deletions.
13 changes: 8 additions & 5 deletions ferveo-python/examples/server_api_precomputed.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ def gen_eth_addr(i: int) -> str:
aad = "my-aad".encode()
ciphertext = encrypt(msg, aad, client_aggregate.public_key)

# In precomputed variant, the client selects a subset of validators to use for decryption
selected_validators = validators[:security_threshold]
selected_keypairs = validator_keypairs[:security_threshold]

# Having aggregated the transcripts, the validators can now create decryption shares
decryption_shares = []
for validator, validator_keypair in zip(validators, validator_keypairs):
for validator, validator_keypair in zip(selected_validators, selected_keypairs):
dkg = Dkg(
tau=tau,
shares_num=shares_num,
Expand All @@ -83,13 +87,12 @@ def gen_eth_addr(i: int) -> str:

# Create a decryption share for the ciphertext
decryption_share = aggregate.create_decryption_share_precomputed(
dkg, ciphertext.header, aad, validator_keypair
dkg, ciphertext.header, aad, validator_keypair, selected_validators
)
decryption_shares.append(decryption_share)

# We need `shares_num` decryption shares in precomputed variant
# TODO: This fails if shares_num != validators_num
decryption_shares = decryption_shares[:validators_num]
# We need at most `security_threshold` decryption shares
decryption_shares = decryption_shares[:security_threshold]

# Now, the decryption share can be used to decrypt the ciphertext
# This part is in the client API
Expand Down
1 change: 1 addition & 0 deletions ferveo-python/ferveo/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class AggregatedTranscript:
ciphertext_header: CiphertextHeader,
aad: bytes,
validator_keypair: Keypair,
selected_validators: Sequence[Validator],
) -> DecryptionSharePrecomputed: ...
@staticmethod
def from_bytes(data: bytes) -> AggregatedTranscript: ...
Expand Down
87 changes: 46 additions & 41 deletions ferveo-python/test/test_ferveo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@
def gen_eth_addr(i: int) -> str:
return f"0x{i:040x}"


def decryption_share_for_variant(v: FerveoVariant, agg_transcript):
if v == FerveoVariant.Simple:
return agg_transcript.create_decryption_share_simple
elif v == FerveoVariant.Precomputed:
return agg_transcript.create_decryption_share_precomputed
else:
raise ValueError("Unknown variant")


def combine_shares_for_variant(v: FerveoVariant, decryption_shares):
if v == FerveoVariant.Simple:
return combine_decryption_shares_simple(decryption_shares)
Expand All @@ -39,19 +29,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 @@ -86,18 +77,27 @@ def scenario_for_variant(
)
server_aggregate = dkg.aggregate_transcripts(messages)
assert server_aggregate.verify(validators_num, messages)

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()
ciphertext = encrypt(msg, aad, client_aggregate.public_key)

# In precomputed variant, the client selects a subset of validators to use for decryption
if variant == FerveoVariant.Precomputed:
selected_validators = validators[:threshold]
selected_validator_keypairs = validator_keypairs[:threshold]
else:
selected_validators = validators
selected_validator_keypairs = validator_keypairs

# Having aggregated the transcripts, the validators can now create decryption shares
decryption_shares = []
for validator, validator_keypair in zip(validators, validator_keypairs):
for validator, validator_keypair in zip(selected_validators, selected_validator_keypairs):
assert validator.public_key == validator_keypair.public_key()
print("validator: ", validator.share_index)

Expand All @@ -108,12 +108,19 @@ def scenario_for_variant(
validators=validators,
me=validator,
)
pvss_aggregated = dkg.aggregate_transcripts(messages)
assert pvss_aggregated.verify(validators_num, messages)

decryption_share = decryption_share_for_variant(variant, pvss_aggregated)(
dkg, ciphertext.header, aad, validator_keypair
)
server_aggregate = dkg.aggregate_transcripts(messages)
assert server_aggregate.verify(validators_num, messages)

if variant == FerveoVariant.Simple:
decryption_share = server_aggregate.create_decryption_share_simple(
dkg, ciphertext.header, aad, validator_keypair
)
elif variant == FerveoVariant.Precomputed:
decryption_share = server_aggregate.create_decryption_share_precomputed(
dkg, ciphertext.header, aad, validator_keypair, selected_validators
)
else:
raise ValueError("Unknown variant")
decryption_shares.append(decryption_share)

# We are limiting the number of decryption shares to use for testing purposes
Expand All @@ -122,12 +129,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 @@ -137,8 +139,8 @@ def scenario_for_variant(


def test_simple_tdec_has_enough_messages():
shares_num = 4
threshold = shares_num - 1
shares_num = 8
threshold = int(shares_num * 2 / 3)
for validators_num in [shares_num, shares_num + 2]:
scenario_for_variant(
FerveoVariant.Simple,
Expand All @@ -150,41 +152,44 @@ def test_simple_tdec_has_enough_messages():


def test_simple_tdec_doesnt_have_enough_messages():
shares_num = 4
threshold = shares_num - 1
shares_num = 8
threshold = int(shares_num * 2 / 3)
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
shares_num = 8
threshold = int(shares_num * 2 / 3)
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
shares_num = 8
threshold = int(shares_num * 2 / 3)
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
14 changes: 10 additions & 4 deletions 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 All @@ -124,10 +124,10 @@ impl SetupSimple {

let pub_contexts = contexts[0].clone().public_decryption_contexts;
let domain: Vec<Fr> = pub_contexts.iter().map(|c| c.domain).collect();
let lagrange = prepare_combine_simple::<E>(&domain);
let lagrange_coeffs = prepare_combine_simple::<E>(&domain);

let shared_secret =
share_combine_simple::<E>(&decryption_shares, &lagrange);
share_combine_simple::<E>(&decryption_shares, &lagrange_coeffs);

let shared = SetupShared {
threshold,
Expand All @@ -144,7 +144,7 @@ impl SetupSimple {
contexts,
pub_contexts,
decryption_shares,
lagrange_coeffs: lagrange,
lagrange_coeffs,
}
}
}
Expand Down Expand Up @@ -200,6 +200,8 @@ pub fn bench_create_decryption_share(c: &mut Criterion) {
};
let simple_precomputed = {
let setup = SetupSimple::new(shares_num, MSG_SIZE_CASES[0], rng);
let selected_participants =
(0..setup.shared.threshold).collect::<Vec<_>>();
move || {
black_box(
setup
Expand All @@ -209,6 +211,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 +298,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 +309,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
Loading

0 comments on commit 18487e6

Please sign in to comment.