Skip to content

Commit

Permalink
feature: introduce refreshing api in ferveo
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Mar 19, 2024
1 parent 4713848 commit b79afcc
Showing 1 changed file with 358 additions and 0 deletions.
358 changes: 358 additions & 0 deletions ferveo/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1395,4 +1395,362 @@ mod test_ferveo_api {
"Shared secret reconstruction failed"
);
}

fn make_share_update_test_inputs(
shares_num: u32,
validators_num: u32,
rng: &mut StdRng,
security_threshold: u32,
) -> (
Vec<ValidatorMessage>,
Vec<Validator>,
Vec<Keypair>,
Vec<Dkg>,
CiphertextHeader,
SharedSecret,
) {
let (messages, validators, validator_keypairs) = make_test_inputs(
rng,
TAU,
security_threshold,
shares_num,
validators_num,
);
let mut dkgs = validators
.iter()
.map(|validator| {
Dkg::new(
TAU,
shares_num,
security_threshold,
&validators,
validator,
)
.unwrap()
})
.collect::<Vec<_>>();
let pvss_aggregated = dkgs[0].aggregate_transcripts(&messages).unwrap();
assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());

// Create an initial shared secret for testing purposes
let public_key = &dkgs[0].public_key();
let ciphertext =
encrypt(SecretBox::new(MSG.to_vec()), AAD, public_key).unwrap();
let ciphertext_header = ciphertext.header().unwrap();
let (_, _, old_shared_secret) =
crate::test_dkg_full::create_shared_secret_simple_tdec(
&dkgs[0].0,
AAD,
&ciphertext_header.0,
validator_keypairs.as_slice(),
);

(
messages,
validators,
validator_keypairs,
dkgs,
ciphertext_header,
SharedSecret(old_shared_secret),
)
}

#[test_case(4, 4, true; "number of shares (validators) is a power of 2")]
#[test_case(7, 7, true; "number of shares (validators) is not a power of 2")]
#[test_case(4, 6, true; "number of validators greater than the number of shares")]
#[test_case(4, 6, false; "recovery at a specific point")]
fn test_dkg_simple_tdec_share_recovery(
shares_num: u32,
validators_num: u32,
recover_at_random_point: bool,
) {
let rng = &mut StdRng::seed_from_u64(0);
let security_threshold = shares_num / 2 + 1;

let (
mut messages,
mut validators,
mut validator_keypairs,
mut dkgs,
ciphertext_header,
old_shared_secret,
) = make_share_update_test_inputs(
shares_num,
validators_num,
rng,
security_threshold,
);

// We assume that all participants have the same aggregate, and that participants created
// their own aggregates before the off-boarding of the validator
// If we didn't create this aggregate here, we risk having a "dangling validator message"
// later when we off-board the validator
let aggregated_transcript =
dkgs[0].clone().aggregate_transcripts(&messages).unwrap();
assert!(aggregated_transcript
.verify(validators_num, &messages)
.unwrap());

// We need to save this domain point to be user in the recovery testing scenario
let mut domain_points = dkgs[0].domain_points();
let removed_domain_point = domain_points.pop().unwrap();

// Remove one participant from the contexts and all nested structure
// to simulate off-boarding a validator
messages.pop().unwrap();
dkgs.pop();
validator_keypairs.pop().unwrap();

let removed_validator = validators.pop().unwrap();
for dkg in dkgs.iter_mut() {
dkg.0
.offboard_validator(&removed_validator.address)
.expect("Unable to off-board a validator from the DKG context");
}

// Now, we're going to recover a new share at a random point or at a specific point
// and check that the shared secret is still the same.
let x_r = if recover_at_random_point {
// Onboarding a validator with a completely new private key share
DomainPoint::<E>::rand(rng)
} else {
// Onboarding a validator with a private key share recovered from the removed validator
removed_domain_point
};

// Each participant prepares an update for each other participant
let share_updates = dkgs
.iter()
.map(|validator_dkg| {
let share_update = ShareRecoveryUpdate::create_share_updates(
validator_dkg,
&x_r,
)
.unwrap();
(validator_dkg.me().address.clone(), share_update)
})
.collect::<HashMap<_, _>>();

// Participants share updates and update their shares

// Now, every participant separately:
let updated_shares: Vec<_> = dkgs
.iter()
.map(|validator_dkg| {
// Current participant receives updates from other participants
let updates_for_participant: Vec<_> = share_updates
.values()
.map(|updates| {
updates
.get(validator_dkg.me().share_index as usize)
.unwrap()
})
.cloned()
.collect();

// Each validator uses their decryption key to update their share
let validator_keypair = validator_keypairs
.get(validator_dkg.me().share_index as usize)
.unwrap();

// And creates updated private key shares
aggregated_transcript
.get_private_key_share(
validator_keypair,
validator_dkg.me().share_index,
)
.unwrap()
.create_updated_private_key_share_for_recovery(
&updates_for_participant,
)
.unwrap()
})
.collect();

// Now, we have to combine new share fragments into a new share
let recovered_key_share =
PrivateKeyShare::recover_share_from_updated_private_shares(
&x_r,
&domain_points,
&updated_shares,
)
.unwrap();

// Get decryption shares from remaining participants
let mut decryption_shares: Vec<DecryptionShareSimple> =
validator_keypairs
.iter()
.zip_eq(dkgs.iter())
.map(|(validator_keypair, validator_dkg)| {
aggregated_transcript
.create_decryption_share_simple(
validator_dkg,
&ciphertext_header,
AAD,
validator_keypair,
)
.unwrap()
})
.collect();
decryption_shares.shuffle(rng);

// In order to test the recovery, we need to create a new decryption share from the recovered
// private key share. To do that, we need a new validator

// Let's create and onboard a new validator
// TODO: Add test scenarios for onboarding and offboarding validators
let new_validator_keypair = Keypair::random();
// Normally, we would get these from the Coordinator:
let new_validator_share_index = removed_validator.share_index;
let new_validator = Validator {
address: gen_address(new_validator_share_index as usize),
public_key: new_validator_keypair.public_key(),
share_index: new_validator_share_index,
};
validators.push(new_validator.clone());
let new_validator_dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
&validators,
&new_validator,
)
.unwrap();

let new_decryption_share = recovered_key_share
.create_decryption_share_simple(
&new_validator_dkg,
&ciphertext_header,
&new_validator_keypair,
AAD,
)
.unwrap();
decryption_shares.push(new_decryption_share);
domain_points.push(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[..security_threshold as usize];
let decryption_shares =
&decryption_shares[..security_threshold as usize];
assert_eq!(domain_points.len(), security_threshold as usize);
assert_eq!(decryption_shares.len(), security_threshold as usize);

let new_shared_secret = combine_shares_simple(decryption_shares);
assert_eq!(
old_shared_secret, new_shared_secret,
"Shared secret reconstruction failed"
);
}

#[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")]
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec_share_refresh(
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,
dkgs,
ciphertext_header,
old_shared_secret,
) = make_share_update_test_inputs(
shares_num,
validators_num,
rng,
security_threshold,
);

// Each participant prepares an update for each other participant
let share_updates = dkgs
.iter()
.map(|validator_dkg| {
let share_update =
ShareRefreshUpdate::create_share_updates(validator_dkg)
.unwrap();
(validator_dkg.me().address.clone(), share_update)
})
.collect::<HashMap<_, _>>();

// Participants share updates and update their shares

// Now, every participant separately:
let updated_shares: Vec<_> = dkgs
.iter()
.map(|validator_dkg| {
// Current participant receives updates from other participants
let updates_for_participant: Vec<_> = share_updates
.values()
.map(|updates| {
updates
.get(validator_dkg.me().share_index as usize)
.unwrap()
})
.cloned()
.collect();

// Each validator uses their decryption key to update their share
let validator_keypair = validator_keypairs
.get(validator_dkg.me().share_index as usize)
.unwrap();

// And creates updated private key shares
// We need an aggregate for that
let aggregate = validator_dkg
.clone()
.aggregate_transcripts(&messages)
.unwrap();
assert!(aggregate.verify(validators_num, &messages).unwrap());

aggregate
.get_private_key_share(
validator_keypair,
validator_dkg.me().share_index,
)
.unwrap()
.create_updated_private_key_share_for_refresh(
&updates_for_participant,
)
.unwrap()
})
.collect();

// Participants create decryption shares
let mut decryption_shares: Vec<DecryptionShareSimple> =
validator_keypairs
.iter()
.zip_eq(dkgs.iter())
.map(|(validator_keypair, validator_dkg)| {
let pks = updated_shares
.get(validator_dkg.me().share_index as usize)
.unwrap()
.clone()
.into_private_key_share();
pks.create_decryption_share_simple(
validator_dkg,
&ciphertext_header,
validator_keypair,
AAD,
)
.unwrap()
})
.collect();
decryption_shares.shuffle(rng);

let decryption_shares =
&decryption_shares[..security_threshold as usize];
assert_eq!(decryption_shares.len(), security_threshold as usize);

let new_shared_secret = combine_shares_simple(decryption_shares);
assert_eq!(
old_shared_secret, new_shared_secret,
"Shared secret reconstruction failed"
);
}
}

0 comments on commit b79afcc

Please sign in to comment.