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

Create an aggregate from subset of messages #185

Merged
merged 2 commits into from
Mar 19, 2024
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
15 changes: 12 additions & 3 deletions ferveo-python/examples/server_api_precomputed.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def gen_eth_addr(i: int) -> str:
)
messages.append(ValidatorMessage(sender, dkg.generate_transcript()))

# We only need `shares_num` messages to aggregate the transcript
messages = messages[:shares_num]

# Every validator can aggregate the transcripts
dkg = Dkg(
tau=tau,
Expand All @@ -61,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 @@ -80,13 +87,15 @@ 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 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

shared_secret = combine_decryption_shares_precomputed(decryption_shares)

# The client should have access to the public parameters of the DKG
Expand Down
6 changes: 6 additions & 0 deletions ferveo-python/examples/server_api_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def gen_eth_addr(i: int) -> str:
)
messages.append(ValidatorMessage(sender, dkg.generate_transcript()))

# We only need `shares_num` messages to aggregate the transcript
messages = messages[:shares_num]

# Now that every validator holds a dkg instance and a transcript for every other validator,
# every validator can aggregate the transcripts
me = validators[0]
Expand Down Expand Up @@ -90,6 +93,9 @@ def gen_eth_addr(i: int) -> str:
)
decryption_shares.append(decryption_share)

# We only need `threshold` decryption shares in simple variant
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
93 changes: 51 additions & 42 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,18 +29,20 @@ def combine_shares_for_variant(v: FerveoVariant, decryption_shares):


def scenario_for_variant(
variant: FerveoVariant, shares_num, validators_num, threshold, 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")

if variant == FerveoVariant.Precomputed and shares_to_use != validators_num:
raise ValueError(
"In precomputed variant, 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 All @@ -72,6 +64,9 @@ def scenario_for_variant(
)
messages.append(ValidatorMessage(sender, dkg.generate_transcript()))

# We only need `shares_num` messages to aggregate the transcript
messages = messages[:shares_num]

# Both client and server should be able to verify the aggregated transcript
dkg = Dkg(
tau=tau,
Expand All @@ -82,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 @@ -104,26 +108,28 @@ 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
# decryption_shares = decryption_shares[:shares_to_use]
decryption_shares = decryption_shares[:dec_shares_to_use]

# 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 @@ -133,54 +139,57 @@ 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,
shares_num=shares_num,
validators_num=validators_num,
threshold=threshold,
shares_to_use=threshold,
dec_shares_to_use=threshold,
)


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,
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,
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,
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
11 changes: 6 additions & 5 deletions ferveo-tdec/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,16 @@ 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::new(
DecryptionSharePrecomputed::create(
self.index,
&self.setup_params.b,
&self.private_key_share,
Expand Down
14 changes: 13 additions & 1 deletion ferveo-tdec/src/decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ impl<E: Pairing> ValidatorShareChecksum<E> {
}
}

/// A decryption share for a simple variant of the threshold decryption scheme.
/// In this variant, the decryption share require additional computation on the
/// client side int order to be combined.
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecryptionShareSimple<E: Pairing> {
Expand Down Expand Up @@ -141,6 +144,11 @@ impl<E: Pairing> DecryptionShareSimple<E> {
}
}

/// A decryption share for a precomputed variant of the threshold decryption scheme.
/// In this variant, the decryption share is precomputed and can be combined
/// without additional computation on the client side.
/// The downside is that the threshold of decryption shares required to decrypt
/// is equal to the number of private key shares in the scheme.
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecryptionSharePrecomputed<E: Pairing> {
Expand All @@ -155,7 +163,9 @@ pub struct DecryptionSharePrecomputed<E: Pairing> {
}

impl<E: Pairing> DecryptionSharePrecomputed<E> {
pub fn new(
/// Create a decryption share from the given parameters.
/// This function checks that the ciphertext is valid.
pub fn create(
validator_index: usize,
validator_decryption_key: &E::ScalarField,
private_key_share: &PrivateKeyShare<E>,
Expand All @@ -174,6 +184,8 @@ impl<E: Pairing> DecryptionSharePrecomputed<E> {
)
}

/// Create a decryption share from the given parameters.
/// This function does not check that the ciphertext is valid.
pub fn create_unchecked(
validator_index: usize,
validator_decryption_key: &E::ScalarField,
Expand Down
Loading
Loading