Skip to content

Commit

Permalink
feat(dkg): relax dkg ceremony constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Jan 15, 2024
1 parent 87c5f34 commit 17a18cd
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 103 deletions.
73 changes: 73 additions & 0 deletions ferveo-python/test/test_ferveo.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,79 @@ def test_precomputed_tdec_doesnt_have_enough_messages():
FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=3
)

def test_dkg_has_min_shares():
total_shares_num = 5
min_shares_num = 3
threshold = 3

tau = 1
validator_keypairs = [Keypair.random() for _ in range(0, total_shares_num)]
validators = [
Validator(gen_eth_addr(i), keypair.public_key())
for i, keypair in enumerate(validator_keypairs)
]
validators.sort(key=lambda v: v.address)

messages = []
for sender in validators:
dkg = Dkg(
tau=tau,
shares_num=min_shares_num,
security_threshold=threshold,
validators=validators,
me=sender,
)
messages.append(ValidatorMessage(sender, dkg.generate_transcript()))

dkg = Dkg(
tau=tau,
shares_num=min_shares_num,
security_threshold=threshold,
validators=validators,
me=validators[0],
)
pvss_aggregated = dkg.aggregate_transcripts(messages)
assert pvss_aggregated.verify(min_shares_num, messages)

dkg_pk_bytes = bytes(dkg.public_key)
dkg_pk = DkgPublicKey.from_bytes(dkg_pk_bytes)

msg = "abc".encode()
aad = "my-aad".encode()
ciphertext = encrypt(msg, aad, dkg_pk)

decryption_shares = []
for validator, validator_keypair in zip(validators, validator_keypairs):
dkg = Dkg(
tau=tau,
shares_num=total_shares_num,
security_threshold=threshold,
validators=validators,
me=validator,
)
pvss_aggregated = dkg.aggregate_transcripts(messages)
assert pvss_aggregated.verify(total_shares_num, messages)

decryption_share = decryption_share_for_variant(variant, pvss_aggregated)(
dkg, ciphertext.header, aad, validator_keypair
)
decryption_shares.append(decryption_share)

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) < total_shares_num:
with pytest.raises(ThresholdEncryptionError):
decrypt_with_shared_secret(ciphertext, aad, shared_secret)
return

plaintext = decrypt_with_shared_secret(ciphertext, aad, shared_secret)
assert bytes(plaintext) == msg


PARAMS = [
(1, FerveoVariant.Simple),
Expand Down
27 changes: 14 additions & 13 deletions ferveo-wasm/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use wasm_bindgen_test::*;

type TestSetup = (
u32,
usize,
usize,
u32,
u32,
Vec<Keypair>,
Vec<Validator>,
ValidatorArray,
Expand All @@ -21,11 +21,12 @@ type TestSetup = (

fn setup_dkg() -> TestSetup {
let tau = 1;
let shares_num = 16;
let shares_num: u32 = 16;
let security_threshold = shares_num * 2 / 3;

let validator_keypairs =
(0..shares_num).map(gen_keypair).collect::<Vec<Keypair>>();
let validator_keypairs = (0..shares_num as usize)
.map(gen_keypair)
.collect::<Vec<Keypair>>();
let validators = validator_keypairs
.iter()
.enumerate()
Expand All @@ -38,8 +39,8 @@ fn setup_dkg() -> TestSetup {
let messages = validators.iter().map(|sender| {
let dkg = Dkg::new(
tau,
shares_num as u32,
security_threshold as u32,
shares_num,
security_threshold,
&validators_js,
sender,
)
Expand All @@ -54,8 +55,8 @@ fn setup_dkg() -> TestSetup {

let mut dkg = Dkg::new(
tau,
shares_num as u32,
security_threshold as u32,
shares_num,
security_threshold,
&validators_js,
&validators[0],
)
Expand Down Expand Up @@ -112,8 +113,8 @@ fn tdec_simple() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
shares_num as u32,
security_threshold as u32,
shares_num,
security_threshold,
&validators_js,
&validator,
)
Expand Down Expand Up @@ -166,8 +167,8 @@ fn tdec_precomputed() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
shares_num as u32,
security_threshold as u32,
shares_num,
security_threshold,
&validators_js,
&validator,
)
Expand Down
6 changes: 1 addition & 5 deletions ferveo/benches/benchmarks/validity_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
&DkgParams {
tau: 0,
security_threshold: shares_num / 3,
shares_num,
},
&DkgParams::new(0, shares_num / 3, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
Expand Down
6 changes: 1 addition & 5 deletions ferveo/examples/bench_primitives_size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
&DkgParams {
tau: 0,
security_threshold,
shares_num,
},
&DkgParams::new(0, security_threshold, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
Expand Down
100 changes: 93 additions & 7 deletions ferveo/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,8 @@ impl Dkg {
validators: &[Validator],
me: &Validator,
) -> Result<Self> {
let dkg_params = crate::DkgParams {
tau,
security_threshold,
shares_num,
};
let dkg_params =
crate::DkgParams::new(tau, security_threshold, shares_num)?;
let dkg = crate::PubliclyVerifiableDkg::<E>::new(
validators,
&dkg_params,
Expand Down Expand Up @@ -312,7 +309,7 @@ impl AggregatedTranscript {
.0
.domain
.elements()
.take(dkg.0.dkg_params.shares_num as usize)
.take(dkg.0.dkg_params.shares_num() as usize)
.collect();
self.0.make_decryption_share_simple_precomputed(
&ciphertext_header.0,
Expand Down Expand Up @@ -627,6 +624,95 @@ mod test_ferveo_api {
}
}

#[test]
fn test_server_api_tdec_simple_with_min_shares() {
let rng = &mut StdRng::seed_from_u64(0);

// 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 - 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 ciphertext =
encrypt(SecretBox::new(msg.clone()), aad, &public_key).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.header().unwrap(),
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]
fn server_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
Expand All @@ -647,7 +733,7 @@ mod test_ferveo_api {

let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
assert!(local_aggregate
.verify(dkg.0.dkg_params.shares_num, &messages)
.verify(dkg.0.dkg_params.shares_num(), &messages)
.is_ok());
}

Expand Down
14 changes: 13 additions & 1 deletion ferveo/src/bindings_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,17 @@ impl From<FerveoPythonError> for PyErr {
}
Error::InvalidVariant(variant) => {
InvalidVariant::new_err(variant.to_string())
}
},
Error::InvalidDkgParameters(num_shares, security_threshold) => {
InvalidDkgParameters::new_err(format!(
"num_shares: {num_shares}, security_threshold: {security_threshold}"
))
},
Error::InvalidShareIndex(index) => {
InvalidShareIndex::new_err(format!(
"{index}"
))
},
},
_ => default(),
}
Expand Down Expand Up @@ -128,6 +138,8 @@ create_exception!(exceptions, ValidatorPublicKeyMismatch, PyValueError);
create_exception!(exceptions, SerializationError, PyValueError);
create_exception!(exceptions, InvalidByteLength, PyValueError);
create_exception!(exceptions, InvalidVariant, PyValueError);
create_exception!(exceptions, InvalidDkgParameters, PyValueError);
create_exception!(exceptions, InvalidShareIndex, PyValueError);

fn from_py_bytes<T: FromBytes>(bytes: &[u8]) -> PyResult<T> {
T::from_bytes(bytes)
Expand Down
8 changes: 3 additions & 5 deletions ferveo/src/bindings_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,15 +510,13 @@ impl AggregatedTranscript {
#[wasm_bindgen]
pub fn verify(
&self,
shares_num: usize,
shares_num: u32,
messages: &ValidatorMessageArray,
) -> JsResult<bool> {
set_panic_hook();
let messages = unwrap_messages_js(messages)?;
let is_valid = self
.0
.verify(shares_num as u32, &messages)
.map_err(map_js_err)?;
let is_valid =
self.0.verify(shares_num, &messages).map_err(map_js_err)?;
Ok(is_valid)
}

Expand Down
Loading

0 comments on commit 17a18cd

Please sign in to comment.