From 975dae0d5f8d1a2e5c061fbc8d11b1cc73c867d7 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 13 Mar 2024 21:37:45 +0100
Subject: [PATCH] fix: not using subset of participants in precomputed variant
---
.../examples/server_api_precomputed.py | 13 +-
ferveo-python/ferveo/__init__.pyi | 1 +
ferveo-python/test/test_ferveo.py | 87 ++++-----
ferveo-tdec/benches/tpke.rs | 14 +-
ferveo-tdec/src/context.rs | 9 +-
ferveo-tdec/src/lib.rs | 78 ++++----
ferveo-wasm/examples/node/src/main.test.ts | 56 +++---
ferveo-wasm/tests/node.rs | 23 ++-
ferveo/src/api.rs | 172 +++++++++---------
ferveo/src/bindings_python.rs | 24 ++-
ferveo/src/bindings_wasm.rs | 8 +
ferveo/src/dkg.rs | 6 +-
ferveo/src/lib.rs | 117 +++++++-----
ferveo/src/pvss.rs | 8 +-
ferveo/src/refresh.rs | 52 ++++--
ferveo/src/test_common.rs | 2 -
16 files changed, 390 insertions(+), 280 deletions(-)
diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py
index 77e21e61..4ebed2ea 100644
--- a/ferveo-python/examples/server_api_precomputed.py
+++ b/ferveo-python/examples/server_api_precomputed.py
@@ -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,
@@ -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
diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi
index 69c77488..30059f4a 100644
--- a/ferveo-python/ferveo/__init__.pyi
+++ b/ferveo-python/ferveo/__init__.pyi
@@ -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: ...
diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py
index 45afe800..6c8fb2a4 100644
--- a/ferveo-python/test/test_ferveo.py
+++ b/ferveo-python/test/test_ferveo.py
@@ -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)
@@ -39,7 +29,11 @@ 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)
@@ -47,11 +41,8 @@ def scenario_for_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)]
@@ -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)
@@ -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
@@ -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
@@ -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,
@@ -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,
)
diff --git a/ferveo-tdec/benches/tpke.rs b/ferveo-tdec/benches/tpke.rs
index b7a5b8f7..03711289 100644
--- a/ferveo-tdec/benches/tpke.rs
+++ b/ferveo-tdec/benches/tpke.rs
@@ -105,7 +105,7 @@ impl SetupSimple {
let aad: &[u8] = "my-aad".as_bytes();
let (pubkey, privkey, contexts) =
- setup_simple::(threshold, shares_num, rng);
+ setup_simple::(shares_num, threshold, rng);
// Ciphertext.commitment is already computed to match U
let ciphertext =
@@ -124,10 +124,10 @@ impl SetupSimple {
let pub_contexts = contexts[0].clone().public_decryption_contexts;
let domain: Vec = pub_contexts.iter().map(|c| c.domain).collect();
- let lagrange = prepare_combine_simple::(&domain);
+ let lagrange_coeffs = prepare_combine_simple::(&domain);
let shared_secret =
- share_combine_simple::(&decryption_shares, &lagrange);
+ share_combine_simple::(&decryption_shares, &lagrange_coeffs);
let shared = SetupShared {
threshold,
@@ -144,7 +144,7 @@ impl SetupSimple {
contexts,
pub_contexts,
decryption_shares,
- lagrange_coeffs: lagrange,
+ lagrange_coeffs,
}
}
}
@@ -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::>();
move || {
black_box(
setup
@@ -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::>(),
@@ -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::>();
let decryption_shares: Vec<_> = setup
.contexts
@@ -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()
})
diff --git a/ferveo-tdec/src/context.rs b/ferveo-tdec/src/context.rs
index ed7faee0..ba697917 100644
--- a/ferveo-tdec/src/context.rs
+++ b/ferveo-tdec/src/context.rs
@@ -92,13 +92,14 @@ impl PrivateDecryptionContextSimple {
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
+ selected_participants: &[usize],
) -> Result> {
- 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::>();
- let lagrange_coeffs = prepare_combine_simple::(&domain);
+ let lagrange_coeffs =
+ prepare_combine_simple::(&selected_domain_points);
DecryptionSharePrecomputed::create(
self.index,
diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs
index e491bba7..e0086dbf 100644
--- a/ferveo-tdec/src/lib.rs
+++ b/ferveo-tdec/src/lib.rs
@@ -175,8 +175,8 @@ pub mod test_common {
}
pub fn setup_simple(
- threshold: usize,
shares_num: usize,
+ threshold: usize,
rng: &mut impl rand::Rng,
) -> (
PublicKey,
@@ -264,17 +264,17 @@ pub mod test_common {
pub fn setup_precomputed(
shares_num: usize,
+ threshold: usize,
rng: &mut impl rand::Rng,
) -> (
PublicKey,
PrivateKeyShare,
Vec>,
) {
- // In precomputed variant, the security threshold is equal to the number of shares
- setup_simple::(shares_num, shares_num, rng)
+ setup_simple::(shares_num, threshold, rng)
}
- pub fn create_shared_secret(
+ pub fn create_shared_secret_simple(
pub_contexts: &[PublicDecryptionContextSimple],
decryption_shares: &[DecryptionShareSimple],
) -> SharedSecret {
@@ -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 = ::TargetField;
@@ -378,7 +382,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();
let (pubkey, _, contexts) =
- setup_simple::(threshold, shares_num, rng);
+ setup_simple::(shares_num, threshold, rng);
let ciphertext =
encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
@@ -447,7 +451,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();
let (pubkey, _, contexts) =
- setup_simple::(threshold, shares_num, &mut rng);
+ setup_simple::(shares_num, threshold, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
@@ -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,
@@ -476,13 +480,18 @@ 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(
+ ¬_enough_contexts,
+ ¬_enough_dec_shares,
+ );
+ let result = decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &bash_shared_secret,
+ g_inv,
+ );
assert!(result.is_err());
}
@@ -490,30 +499,39 @@ mod tests {
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::(shares_num, &mut rng);
+ setup_precomputed::(shares_num, threshold, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
encrypt::(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::>();
+
+ let decryption_shares = selected_contexts
.iter()
.map(|context| {
context
.create_share_precomputed(
&ciphertext.header().unwrap(),
aad,
+ &selected_participants,
)
.unwrap()
})
- .collect();
+ .collect::>();
let shared_secret = share_combine_precomputed::(&decryption_shares);
-
test_ciphertext_validation_fails(
&msg,
aad,
@@ -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::(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(¬_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]
@@ -546,7 +562,7 @@ mod tests {
let aad: &[u8] = "my-aad".as_bytes();
let (pubkey, _, contexts) =
- setup_simple::(threshold, shares_num, &mut rng);
+ setup_simple::(shares_num, threshold, &mut rng);
let ciphertext =
encrypt::(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts
index 57071421..ba5361c3 100644
--- a/ferveo-wasm/examples/node/src/main.test.ts
+++ b/ferveo-wasm/examples/node/src/main.test.ts
@@ -52,10 +52,9 @@ function setupTest(
// every validator can aggregate the transcripts
const dkg = new Dkg(TAU, sharesNum, threshold, validators, validators[0]);
+ // Both the server and the client can aggregate the transcripts and verify them
const serverAggregate = dkg.aggregateTranscript(messages);
expect(serverAggregate.verify(validatorsNum, messages)).toBe(true);
-
- // Client can also aggregate the transcripts and verify them
const clientAggregate = new AggregatedTranscript(messages);
expect(clientAggregate.verify(validatorsNum, messages)).toBe(true);
@@ -81,8 +80,14 @@ describe("ferveo-wasm", () => {
const sharesNum = 4;
const threshold = sharesNum - 1;
[sharesNum, sharesNum + 2].forEach((validatorsNum) => {
- const { validatorKeypairs, validators, messages, msg, aad, ciphertext } =
- setupTest(sharesNum, validatorsNum, threshold);
+ const {
+ validatorKeypairs,
+ validators,
+ messages,
+ msg,
+ aad,
+ ciphertext
+ } = setupTest(sharesNum, validatorsNum, threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
const decryptionShares: DecryptionShareSimple[] = [];
@@ -90,11 +95,11 @@ describe("ferveo-wasm", () => {
expect(validator.publicKey.equals(keypair.publicKey)).toBe(true);
const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator);
- const aggregate = dkg.aggregateTranscript(messages);
- const isValid = aggregate.verify(validatorsNum, messages);
+ const serverAggregate = dkg.aggregateTranscript(messages);
+ const isValid = serverAggregate.verify(validatorsNum, messages);
expect(isValid).toBe(true);
- const decryptionShare = aggregate.createDecryptionShareSimple(
+ const decryptionShare = serverAggregate.createDecryptionShareSimple(
dkg,
ciphertext.header,
aad,
@@ -105,49 +110,52 @@ describe("ferveo-wasm", () => {
// Now, the decryption share can be used to decrypt the ciphertext
// This part is in the client API
-
const sharedSecret = combineDecryptionSharesSimple(decryptionShares);
-
- // The client should have access to the public parameters of the DKG
-
const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret);
expect(Buffer.from(plaintext)).toEqual(msg);
});
});
it("precomputed tdec variant", () => {
- const sharesNum = 4;
- const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant
+ const sharesNum = 8;
+ const threshold = sharesNum * 2 / 3;
[sharesNum, sharesNum + 2].forEach((validatorsNum) => {
- const { validatorKeypairs, validators, messages, msg, aad, ciphertext } =
- setupTest(sharesNum, validatorsNum, threshold);
+ const {
+ validatorKeypairs,
+ validators,
+ messages,
+ msg,
+ aad,
+ ciphertext
+ } = setupTest(sharesNum, validatorsNum, threshold);
+
+ // In precomputed variant, client selects a subset of validators to create decryption shares
+ const selectedValidators = validators.slice(0, threshold);
+ const selectedValidatorKeypairs = validatorKeypairs.slice(0, threshold);
// Having aggregated the transcripts, the validators can now create decryption shares
const decryptionShares: DecryptionSharePrecomputed[] = [];
- zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
+ zip(selectedValidators, selectedValidatorKeypairs).forEach(([validator, keypair]) => {
expect(validator.publicKey.equals(keypair.publicKey)).toBe(true);
const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator);
- const aggregate = dkg.aggregateTranscript(messages);
- const isValid = aggregate.verify(validatorsNum, messages);
+ const serverAggregate = dkg.aggregateTranscript(messages);
+ const isValid = serverAggregate.verify(validatorsNum, messages);
expect(isValid).toBe(true);
- const decryptionShare = aggregate.createDecryptionSharePrecomputed(
+ const decryptionShare = serverAggregate.createDecryptionSharePrecomputed(
dkg,
ciphertext.header,
aad,
- keypair
+ keypair,
+ selectedValidators,
);
decryptionShares.push(decryptionShare);
});
// Now, the decryption share can be used to decrypt the ciphertext
// This part is in the client API
-
const sharedSecret = combineDecryptionSharesPrecomputed(decryptionShares);
-
- // The client should have access to the public parameters of the DKG
-
const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret);
expect(Buffer.from(plaintext)).toEqual(msg);
});
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index d3a5ea43..cddde011 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -155,7 +155,7 @@ fn tdec_simple() {
#[wasm_bindgen_test]
fn tdec_precomputed() {
let shares_num = 16;
- let security_threshold = shares_num; // Must be equal to shares_num in precomputed variant
+ let security_threshold = shares_num * 2 / 3;
for validators_num in [shares_num, shares_num + 2] {
let (
validator_keypairs,
@@ -167,6 +167,11 @@ fn tdec_precomputed() {
ciphertext,
) = setup_dkg(shares_num, validators_num, security_threshold);
+ // In precomputed variant, the client selects a subset of validators to create decryption shares
+ let selected_validators =
+ validators[..(security_threshold as usize)].to_vec();
+ let selected_validators_js = into_js_array(selected_validators);
+
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares = zip_eq(validators, validator_keypairs)
.map(|(validator, keypair)| {
@@ -178,23 +183,23 @@ fn tdec_precomputed() {
&validator,
)
.unwrap();
- let aggregate =
+ let server_aggregate =
dkg.aggregate_transcripts(&messages_js).unwrap();
- let is_valid =
- aggregate.verify(validators_num, &messages_js).unwrap();
- assert!(is_valid);
- aggregate
+ assert!(server_aggregate
+ .verify(validators_num, &messages_js)
+ .unwrap());
+ server_aggregate
.create_decryption_share_precomputed(
&dkg,
&ciphertext.header().unwrap(),
&aad,
&keypair,
+ &selected_validators_js,
)
.unwrap()
})
- // We need `shares_num` decryption shares in precomputed variant
- // TODO: This fails if shares_num != validators_num
- .take(validators_num as usize)
+ // We need `security_threshold` decryption shares to decrypt
+ .take(security_threshold as usize)
.collect::>();
let decryption_shares_js = into_js_array(decryption_shares);
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 7ab62c56..637750cc 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -308,22 +308,23 @@ impl AggregatedTranscript {
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
+ selected_validators: &[Validator],
) -> Result {
- // Prevent users from using the precomputed variant with improper DKG parameters
- if dkg.0.dkg_params.shares_num()
- != dkg.0.dkg_params.security_threshold()
- {
- return Err(Error::InvalidDkgParametersForPrecomputedVariant(
- dkg.0.dkg_params.shares_num(),
- dkg.0.dkg_params.security_threshold(),
- ));
- }
- self.0.aggregate.create_decryption_share_simple_precomputed(
+ let selected_domain_points = selected_validators
+ .iter()
+ .filter_map(|v| {
+ dkg.0
+ .get_domain_point(v.share_index)
+ .ok()
+ .map(|domain_point| (v.share_index, domain_point))
+ })
+ .collect::>>();
+ self.0.aggregate.create_decryption_share_precomputed(
&ciphertext_header.0,
aad,
validator_keypair,
dkg.0.me.share_index,
- &dkg.0.domain_points(),
+ &selected_domain_points,
)
}
@@ -544,22 +545,21 @@ impl PrivateKeyShare {
}
/// Make a decryption share (precomputed variant) for a given ciphertext
- pub fn create_decryption_share_simple_precomputed(
+ pub fn create_decryption_share_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
- domain_points: &[DomainPoint],
+ domain_points: &HashMap,
) -> Result {
- let share = self.0.create_decryption_share_simple_precomputed(
+ self.0.create_decryption_share_precomputed(
&ciphertext_header.0,
aad,
validator_keypair,
share_index,
domain_points,
- )?;
- Ok(share)
+ )
}
pub fn to_bytes(&self) -> Result> {
@@ -641,10 +641,8 @@ mod test_ferveo_api {
#[test_case(7, 7; "number of shares (validators) is not a power of 2")]
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) {
+ let security_threshold = shares_num * 2 / 3;
let rng = &mut StdRng::seed_from_u64(0);
-
- // In precomputed variant, the security threshold is equal to the number of shares
- let security_threshold = shares_num;
let (messages, validators, validator_keypairs) = make_test_inputs(
rng,
TAU,
@@ -660,52 +658,65 @@ mod test_ferveo_api {
let dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
- let pvss_aggregated = dkg.aggregate_transcripts(messages).unwrap();
- assert!(pvss_aggregated.verify(validators_num, messages).unwrap());
+ let local_aggregate = dkg.aggregate_transcripts(messages).unwrap();
+ assert!(local_aggregate.verify(validators_num, messages).unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = pvss_aggregated.public_key();
+ let dkg_public_key = local_aggregate.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext =
encrypt(SecretBox::new(MSG.to_vec()), AAD, &dkg_public_key)
.unwrap();
+ // In precomputed variant, client selects a specific subset of validators to create
+ // decryption shares
+ let selected_validators: Vec<_> = validators
+ .choose_multiple(rng, security_threshold as usize)
+ .cloned()
+ .collect();
+
// Having aggregated the transcripts, the validators can now create decryption shares
- let mut 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 dkg = Dkg::new(
- TAU,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
+ let mut decryption_shares = selected_validators
+ .iter()
+ .map(|validator| {
+ let validator_keypair = validator_keypairs
+ .iter()
+ .find(|kp| kp.public_key() == validator.public_key)
.unwrap();
- let aggregate =
- dkg.aggregate_transcripts(messages).unwrap();
- assert!(pvss_aggregated
- .verify(validators_num, messages)
- .unwrap());
-
- // And then each validator creates their own decryption share
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext.header().unwrap(),
- AAD,
- validator_keypair,
- )
- .unwrap()
- })
- .collect();
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap();
+ let server_aggregate =
+ dkg.aggregate_transcripts(messages).unwrap();
+ assert!(server_aggregate
+ .verify(validators_num, messages)
+ .unwrap());
+
+ // And then each validator creates their own decryption share
+ server_aggregate
+ .create_decryption_share_precomputed(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ AAD,
+ validator_keypair,
+ &selected_validators,
+ )
+ .unwrap()
+ })
+ // We only need `security_threshold` shares to be able to decrypt
+ .take(security_threshold as usize)
+ .collect::>();
decryption_shares.shuffle(rng);
// 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,
@@ -715,10 +726,13 @@ mod test_ferveo_api {
.unwrap();
assert_eq!(plaintext, MSG);
- // Since we're using a precomputed variant, we need all the shares to be able to decrypt
+ // We need `security_threshold` 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 decryption_shares = decryption_shares
+ .iter()
+ .take(security_threshold as usize - 1)
+ .cloned()
+ .collect::>();
let shared_secret = share_combine_precomputed(&decryption_shares);
let result = decrypt_with_shared_secret(
&ciphertext,
@@ -733,7 +747,6 @@ mod test_ferveo_api {
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
-
let security_threshold = shares_num / 2 + 1;
let (messages, validators, validator_keypairs) = make_test_inputs(
rng,
@@ -747,19 +760,11 @@ mod test_ferveo_api {
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
- let dkg = Dkg::new(
- TAU,
- shares_num,
- security_threshold,
- &validators,
- &validators[0],
- )
- .unwrap();
- let pvss_aggregated = dkg.aggregate_transcripts(messages).unwrap();
- assert!(pvss_aggregated.verify(validators_num, messages).unwrap());
+ let local_aggregate = AggregatedTranscript::new(messages).unwrap();
+ assert!(local_aggregate.verify(validators_num, messages).unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let public_key = pvss_aggregated.public_key();
+ let public_key = local_aggregate.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext =
@@ -778,12 +783,12 @@ mod test_ferveo_api {
validator,
)
.unwrap();
- let aggregate =
+ let server_aggregate =
dkg.aggregate_transcripts(messages).unwrap();
- assert!(aggregate
+ assert!(server_aggregate
.verify(validators_num, messages)
.unwrap());
- aggregate
+ server_aggregate
.create_decryption_share_simple(
&dkg,
&ciphertext.header().unwrap(),
@@ -792,13 +797,13 @@ mod test_ferveo_api {
)
.unwrap()
})
+ // We only need `security_threshold` shares to be able to decrypt
+ .take(security_threshold as usize)
.collect();
decryption_shares.shuffle(rng);
// 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();
@@ -808,8 +813,8 @@ mod test_ferveo_api {
.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
+ // We need `security_threshold` shares to be able to decrypt
+ // So if we remove one share, we should not be able to decrypt
let decryption_shares =
decryption_shares[..security_threshold as usize - 1].to_vec();
@@ -819,9 +824,9 @@ mod test_ferveo_api {
assert!(result.is_err());
}
- // Note that the server and client code are using the same underlying
- // implementation for aggregation and aggregate verification.
- // Here, we focus on testing user-facing APIs for server and client users.
+ /// Note that the server and client code are using the same underlying
+ /// implementation for aggregation and aggregate verification.
+ /// Here, we focus on testing user-facing APIs for server and client users.
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
#[test_case(7, 7; "number of shares (validators) is not a power of 2")]
@@ -829,7 +834,6 @@ mod test_ferveo_api {
fn server_side_local_verification(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;
-
let (messages, validators, _) = make_test_inputs(
rng,
TAU,
@@ -940,7 +944,6 @@ mod test_ferveo_api {
fn client_side_local_verification(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;
-
let (messages, _, _) = make_test_inputs(
rng,
TAU,
@@ -1041,11 +1044,11 @@ mod test_ferveo_api {
// Creating a copy to avoiding accidentally changing DKG state
let dkg = dkgs[0].clone();
- let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());
+ let server_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(server_aggregate.verify(validators_num, &messages).unwrap());
// Create an initial shared secret for testing purposes
- let public_key = pvss_aggregated.public_key();
+ let public_key = server_aggregate.public_key();
let ciphertext =
encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap();
let ciphertext_header = ciphertext.header().unwrap();
@@ -1062,7 +1065,6 @@ mod test_ferveo_api {
validator_keypairs.as_slice(),
&transcripts,
);
-
(
messages,
validators,
@@ -1084,7 +1086,6 @@ mod test_ferveo_api {
) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;
-
let (
mut messages,
mut validators,
@@ -1240,8 +1241,6 @@ mod test_ferveo_api {
.unwrap();
decryption_shares.push(new_decryption_share);
domain_points.insert(new_validator_share_index, x_r);
- assert_eq!(domain_points.len(), validators_num as usize);
- assert_eq!(decryption_shares.len(), validators_num as usize);
let domain_points = domain_points
.values()
@@ -1269,7 +1268,6 @@ mod test_ferveo_api {
) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;
-
let (
messages,
_validators,
@@ -1355,6 +1353,8 @@ mod test_ferveo_api {
)
.unwrap()
})
+ // We only need `security_threshold` shares to be able to decrypt
+ .take(security_threshold as usize)
.collect();
decryption_shares.shuffle(rng);
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index 0689a495..bd189e7e 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -621,7 +621,10 @@ impl AggregatedTranscript {
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
+ selected_validators: Vec,
) -> PyResult {
+ let selected_validators: Vec<_> =
+ selected_validators.into_iter().map(|v| v.0).collect();
let decryption_share = self
.0
.create_decryption_share_precomputed(
@@ -629,6 +632,7 @@ impl AggregatedTranscript {
&ciphertext_header.0,
aad,
&validator_keypair.0,
+ &selected_validators,
)
.map_err(FerveoPythonError::FerveoError)?;
Ok(DecryptionSharePrecomputed(decryption_share))
@@ -841,9 +845,7 @@ mod test_ferveo_python {
#[test_case(4, 4; "number of validators equal to the number of shares")]
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) {
- // In precomputed variant, the security threshold is equal to the number of shares
- let security_threshold = shares_num;
-
+ let security_threshold = shares_num * 2 / 3;
let (messages, validators, validator_keypairs) = make_test_inputs(
TAU,
security_threshold,
@@ -866,18 +868,21 @@ mod test_ferveo_python {
// Let's say that we've only received `security_threshold` transcripts
let messages = messages[..security_threshold as usize].to_vec();
- let pvss_aggregated =
+ let local_aggregate =
dkg.aggregate_transcripts(messages.clone()).unwrap();
- assert!(pvss_aggregated
+ assert!(local_aggregate
.verify(validators_num, messages.clone())
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = pvss_aggregated.public_key();
+ let dkg_public_key = local_aggregate.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap();
+ // TODO: Adjust the subset of validators to be used in the decryption for precomputed
+ // variant
+
// Having aggregated the transcripts, the validators can now create decryption shares
let decryption_shares: Vec<_> =
izip!(validators.clone(), &validator_keypairs)
@@ -891,18 +896,19 @@ mod test_ferveo_python {
&validator,
)
.unwrap();
- let aggregate = validator_dkg
+ let server_aggregate = validator_dkg
.aggregate_transcripts(messages.clone())
.unwrap();
- assert!(pvss_aggregated
+ assert!(server_aggregate
.verify(validators_num, messages.clone())
.is_ok());
- aggregate
+ server_aggregate
.create_decryption_share_precomputed(
&validator_dkg,
&ciphertext.header().unwrap(),
AAD,
validator_keypair,
+ validators.clone(),
)
.unwrap()
})
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index 56325092..0b369874 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -536,8 +536,15 @@ impl AggregatedTranscript {
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
+ selected_validators_js: &ValidatorArray,
) -> JsResult {
set_panic_hook();
+ let selected_validators =
+ try_from_js_array::(selected_validators_js)?;
+ let selected_validators = selected_validators
+ .into_iter()
+ .map(|v| v.to_inner())
+ .collect::>>()?;
let decryption_share = self
.0
.create_decryption_share_precomputed(
@@ -545,6 +552,7 @@ impl AggregatedTranscript {
&ciphertext_header.0,
aad,
&validator_keypair.0,
+ &selected_validators,
)
.map_err(map_js_err)?;
Ok(DecryptionSharePrecomputed(decryption_share))
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index d2e825a7..360e5202 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -169,10 +169,10 @@ impl PubliclyVerifiableDkg {
/// Return a map of domain points for the DKG
pub fn domain_point_map(&self) -> HashMap> {
- self.domain_points()
- .iter()
+ self.domain
+ .elements()
.enumerate()
- .map(|(i, point)| (i as u32, *point))
+ .map(|(i, point)| (i as u32, point))
.collect::>()
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 67e501fe..e3638fe6 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -138,9 +138,9 @@ mod test_dkg_full {
Vec>,
SharedSecret,
) {
- let pvss_aggregated =
+ let server_aggregate =
AggregatedTranscript::from_transcripts(transcripts).unwrap();
- assert!(pvss_aggregated
+ assert!(server_aggregate
.aggregate
.verify_aggregation(dkg, transcripts)
.unwrap());
@@ -152,7 +152,7 @@ mod test_dkg_full {
let validator = dkg
.get_validator(&validator_keypair.public_key())
.unwrap();
- pvss_aggregated
+ server_aggregate
.aggregate
.create_decryption_share_simple(
ciphertext_header,
@@ -175,7 +175,7 @@ mod test_dkg_full {
&decryption_shares,
&lagrange_coeffs,
);
- (pvss_aggregated, decryption_shares, shared_secret)
+ (server_aggregate, decryption_shares, shared_secret)
}
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
@@ -183,8 +183,7 @@ mod test_dkg_full {
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
-
- let security_threshold = shares_num / 2 + 1;
+ let security_threshold = shares_num * 2 / 3;
let (dkg, validator_keypairs, messages) =
setup_dealt_dkg_with_n_validators(
security_threshold,
@@ -196,17 +195,19 @@ mod test_dkg_full {
.take(shares_num as usize)
.map(|m| m.1.clone())
.collect::>();
- let public_key = AggregatedTranscript::from_transcripts(&transcripts)
- .unwrap()
- .public_key;
+ let local_aggregate =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ assert!(local_aggregate
+ .aggregate
+ .verify_aggregation(&dkg, &transcripts)
+ .unwrap());
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- &public_key,
+ &local_aggregate.public_key,
rng,
)
.unwrap();
-
let (_, _, shared_secret) = create_shared_secret_simple_tdec(
&dkg,
AAD,
@@ -227,62 +228,77 @@ mod test_dkg_full {
#[test_case(4, 4; "number of shares (validators) is a power of 2")]
#[test_case(7, 7; "number of shares (validators) is not a power of 2")]
- // TODO: This test fails:
- // #[test_case(4, 6; "number of validators greater than the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
-
- // In precomputed variant, threshold must be equal to shares_num
- let security_threshold = shares_num;
+ let security_threshold = shares_num * 2 / 3;
let (dkg, validator_keypairs, messages) =
- setup_dealt_dkg_with_n_validators(
+ setup_dealt_dkg_with_n_transcript_dealt(
security_threshold,
shares_num,
validators_num,
+ shares_num,
);
let transcripts = messages
.iter()
.take(shares_num as usize)
.map(|m| m.1.clone())
.collect::>();
- let pvss_aggregated =
+ let local_aggregate =
AggregatedTranscript::from_transcripts(&transcripts).unwrap();
- assert!(pvss_aggregated
+ assert!(local_aggregate
.aggregate
.verify_aggregation(&dkg, &transcripts)
.unwrap());
- let public_key = pvss_aggregated.public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- &public_key,
+ &local_aggregate.public_key,
rng,
)
.unwrap();
+ // In precomputed variant, client selects a specific subset of validators to create
+ // decryption shares
+ let selected_keypairs = validator_keypairs
+ .choose_multiple(rng, security_threshold as usize)
+ .collect::>();
+ let selected_validators = selected_keypairs
+ .iter()
+ .map(|keypair| {
+ dkg.get_validator(&keypair.public_key())
+ .expect("Validator not found")
+ })
+ .collect::>();
+ let selected_domain_points = selected_validators
+ .iter()
+ .filter_map(|v| {
+ dkg.get_domain_point(v.share_index)
+ .ok()
+ .map(|domain_point| (v.share_index, domain_point))
+ })
+ .collect::>>();
+
let mut decryption_shares: Vec> =
- validator_keypairs
+ selected_keypairs
.iter()
.map(|validator_keypair| {
let validator = dkg
.get_validator(&validator_keypair.public_key())
.unwrap();
- pvss_aggregated
+ local_aggregate
.aggregate
- .create_decryption_share_simple_precomputed(
+ .create_decryption_share_precomputed(
&ciphertext.header().unwrap(),
AAD,
validator_keypair,
validator.share_index,
- &dkg.domain_points(),
+ &selected_domain_points,
)
.unwrap()
})
- // We take only the first `security_threshold` decryption shares
- .take(dkg.dkg_params.security_threshold() as usize)
.collect();
-
- // Order of decryption shares is not important in the precomputed variant
+ // Order of decryption shares is not important
decryption_shares.shuffle(rng);
// Decrypt with precomputed variant
@@ -307,7 +323,6 @@ mod test_dkg_full {
) {
let rng = &mut test_rng();
let security_threshold = shares_num / 2 + 1;
-
let (dkg, validator_keypairs, messages) =
setup_dealt_dkg_with_n_validators(
security_threshold,
@@ -319,18 +334,21 @@ mod test_dkg_full {
.take(shares_num as usize)
.map(|m| m.1.clone())
.collect::>();
- let public_key = AggregatedTranscript::from_transcripts(&transcripts)
- .unwrap()
- .public_key;
+ let local_aggregate =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ assert!(local_aggregate
+ .aggregate
+ .verify_aggregation(&dkg, &transcripts)
+ .unwrap());
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- &public_key,
+ &local_aggregate.public_key,
rng,
)
.unwrap();
- let (pvss_aggregated, decryption_shares, _) =
+ let (local_aggregate, decryption_shares, _) =
create_shared_secret_simple_tdec(
&dkg,
AAD,
@@ -340,7 +358,7 @@ mod test_dkg_full {
);
izip!(
- &pvss_aggregated.aggregate.shares,
+ &local_aggregate.aggregate.shares,
&validator_keypairs,
&decryption_shares,
)
@@ -362,7 +380,7 @@ mod test_dkg_full {
let mut with_bad_decryption_share = decryption_share.clone();
with_bad_decryption_share.decryption_share = TargetField::zero();
assert!(!with_bad_decryption_share.verify(
- &pvss_aggregated.aggregate.shares[0],
+ &local_aggregate.aggregate.shares[0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
@@ -372,7 +390,7 @@ mod test_dkg_full {
let mut with_bad_checksum = decryption_share;
with_bad_checksum.validator_checksum.checksum = G1Affine::zero();
assert!(!with_bad_checksum.verify(
- &pvss_aggregated.aggregate.shares[0],
+ &local_aggregate.aggregate.shares[0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
@@ -388,7 +406,6 @@ mod test_dkg_full {
) {
let rng = &mut test_rng();
let security_threshold = shares_num / 2 + 1;
-
let (dkg, validator_keypairs, messages) =
setup_dealt_dkg_with_n_validators(
security_threshold,
@@ -400,13 +417,16 @@ mod test_dkg_full {
.take(shares_num as usize)
.map(|m| m.1.clone())
.collect::>();
- let public_key = AggregatedTranscript::from_transcripts(&transcripts)
- .unwrap()
- .public_key;
+ let local_aggregate =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ assert!(local_aggregate
+ .aggregate
+ .verify_aggregation(&dkg, &transcripts)
+ .unwrap());
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- &public_key,
+ &local_aggregate.public_key,
rng,
)
.unwrap();
@@ -598,13 +618,16 @@ mod test_dkg_full {
.take(shares_num as usize)
.map(|m| m.1.clone())
.collect::>();
- let public_key = AggregatedTranscript::from_transcripts(&transcripts)
- .unwrap()
- .public_key;
+ let local_aggregate =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ assert!(local_aggregate
+ .aggregate
+ .verify_aggregation(&dkg, &transcripts)
+ .unwrap());
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- &public_key,
+ &local_aggregate.public_key,
rng,
)
.unwrap();
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 700db9cb..8d1affeb 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -1,4 +1,4 @@
-use std::{hash::Hash, marker::PhantomData, ops::Mul};
+use std::{collections::HashMap, hash::Hash, marker::PhantomData, ops::Mul};
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group};
use ark_ff::{Field, Zero};
@@ -358,16 +358,16 @@ impl PubliclyVerifiableSS {
/// Make a decryption share (precomputed variant) for a given ciphertext
/// With this method, we wrap the PrivateKeyShare method to avoid exposing the private key share
// TODO: Consider deprecating to use PrivateKeyShare method directly
- pub fn create_decryption_share_simple_precomputed(
+ pub fn create_decryption_share_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
- domain_points: &[DomainPoint],
+ domain_points: &HashMap>,
) -> Result> {
self.decrypt_private_key_share(validator_keypair, share_index)?
- .create_decryption_share_simple_precomputed(
+ .create_decryption_share_precomputed(
ciphertext_header,
aad,
validator_keypair,
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index 0b8a95ef..d7700cfa 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -102,21 +102,51 @@ impl PrivateKeyShare {
.map_err(|e| e.into())
}
- pub fn create_decryption_share_simple_precomputed(
+ /// In precomputed variant, we offload some of the decryption related computation to the server-side:
+ /// We use the `prepare_combine_simple` function to precompute the lagrange coefficients
+ pub fn create_decryption_share_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
- domain_points: &[DomainPoint],
+ domain_points_map: &HashMap>,
) -> Result> {
- let g_inv = PubliclyVerifiableParams::::default().g_inv();
- // In precomputed variant, we offload some of the decryption related computation to the server-side:
- // We use the `prepare_combine_simple` function to precompute the lagrange coefficients
- let lagrange_coeffs = prepare_combine_simple::(domain_points);
- let lagrange_coeff = &lagrange_coeffs
- .get(share_index as usize)
+ // We need to turn the domain points into a vector, and sort it by share index
+ let mut domain_points = domain_points_map
+ .iter()
+ .map(|(share_index, domain_point)| (*share_index, *domain_point))
+ .collect::>();
+ domain_points.sort_by_key(|(share_index, _)| *share_index);
+
+ // Now, we have to pass the domain points to the `prepare_combine_simple` function
+ // and use the resulting lagrange coefficients to create the decryption share
+
+ let only_domain_points = domain_points
+ .iter()
+ .map(|(_, domain_point)| *domain_point)
+ .collect::>();
+ let lagrange_coeffs = prepare_combine_simple::(&only_domain_points);
+
+ // Before we pick the lagrange coefficient for the current share index, we need
+ // to map the share index to the index in the domain points vector
+ // Given that we sorted the domain points by share index, the first element in the vector
+ // will correspond to the smallest share index, second to the second smallest, and so on
+
+ let sorted_share_indices = domain_points
+ .iter()
+ .enumerate()
+ .map(|(adjusted_share_index, (share_index, _))| {
+ (*share_index, adjusted_share_index)
+ })
+ .collect::>();
+ let adjusted_share_index = *sorted_share_indices
+ .get(&share_index)
.ok_or(Error::InvalidShareIndex(share_index))?;
+
+ // Finally, pick the lagrange coefficient for the current share index
+ let lagrange_coeff = &lagrange_coeffs[adjusted_share_index];
+ let g_inv = PubliclyVerifiableParams::::default().g_inv();
DecryptionSharePrecomputed::create(
share_index as usize,
&validator_keypair.decryption_key,
@@ -368,8 +398,8 @@ mod tests_refresh {
let security_threshold = shares_num * 2 / 3;
let (_, _, mut contexts) = setup_simple::(
- security_threshold as usize,
shares_num as usize,
+ security_threshold as usize,
rng,
);
@@ -447,8 +477,8 @@ mod tests_refresh {
let security_threshold = shares_num * 2 / 3;
let (_, shared_private_key, mut contexts) = setup_simple::(
- security_threshold as usize,
shares_num as usize,
+ security_threshold as usize,
rng,
);
@@ -537,7 +567,7 @@ mod tests_refresh {
let security_threshold = shares_num * 2 / 3;
let (_, private_key_share, contexts) =
- setup_simple::(security_threshold, shares_num, rng);
+ setup_simple::(shares_num, security_threshold, rng);
let domain_points = &contexts
.iter()
.map(|ctxt| {
diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs
index eea3a7da..a1a3d130 100644
--- a/ferveo/src/test_common.rs
+++ b/ferveo/src/test_common.rs
@@ -90,8 +90,6 @@ pub fn setup_dealt_dkg() -> DealtTestSetup {
setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM)
}
-// TODO: Rewrite setup_utils to return messages separately
-
pub fn setup_dealt_dkg_with(
security_threshold: u32,
shares_num: u32,