#![warn(rust_2018_idioms)]
pub mod ciphertext;
pub mod combine;
pub mod context;
pub mod decryption;
pub mod hash_to_curve;
pub mod key_share;
pub mod refresh;
pub mod secret_box;
pub use ciphertext::*;
pub use combine::*;
pub use context::*;
pub use decryption::*;
pub use hash_to_curve::*;
pub use key_share::*;
pub use refresh::*;
pub use secret_box::*;
#[cfg(feature = "api")]
pub mod api;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Ciphertext verification failed")]
CiphertextVerificationFailed,
#[error("Decryption share verification failed")]
DecryptionShareVerificationFailed,
#[error("Symmetric encryption failed")]
SymmetricEncryptionError(chacha20poly1305::aead::Error),
#[error(transparent)]
BincodeError(#[from] bincode::Error),
#[error(transparent)]
ArkSerializeError(#[from] ark_serialize::SerializationError),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(any(test, feature = "test-common"))]
pub mod test_common {
use std::{ops::Mul, usize};
pub use ark_bls12_381::Bls12_381 as EllipticCurve;
use ark_ec::{pairing::Pairing, AffineRepr};
pub use ark_ff::UniformRand;
use ark_ff::{Field, One, Zero};
use ark_poly::{
univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain,
Polynomial,
};
use itertools::izip;
use rand_core::RngCore;
use subproductdomain::fast_multiexp;
pub use super::*;
pub fn setup_fast<E: Pairing>(
threshold: usize,
shares_num: usize,
rng: &mut impl RngCore,
) -> (
E::G1Affine,
E::G2Affine,
Vec<PrivateDecryptionContextFast<E>>,
) {
assert!(shares_num >= threshold);
let g = E::G1Affine::generator();
let h = E::G2Affine::generator();
let threshold_poly =
DensePolynomial::<E::ScalarField>::rand(threshold - 1, rng);
let fft_domain =
ark_poly::GeneralEvaluationDomain::<E::ScalarField>::new(
shares_num,
)
.unwrap();
let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain);
let pubkey_shares = fast_multiexp(&evals.evals, g.into_group());
let pubkey_share = g.mul(evals.evals[0]);
debug_assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share));
let privkey_shares = fast_multiexp(&evals.evals, h.into_group());
let x = threshold_poly.coeffs[0];
let pubkey = g.mul(x);
let privkey = h.mul(x);
let mut domain_points = Vec::with_capacity(shares_num);
let mut point = E::ScalarField::one();
let mut domain_points_inv = Vec::with_capacity(shares_num);
let mut point_inv = E::ScalarField::one();
for _ in 0..shares_num {
domain_points.push(point); point *= fft_domain.group_gen();
domain_points_inv.push(point_inv);
point_inv *= fft_domain.group_gen_inv();
}
let mut private_contexts = vec![];
let mut public_contexts = vec![];
for (index, (domain, domain_inv, public, private)) in izip!(
domain_points.iter(),
domain_points_inv.iter(),
pubkey_shares.iter(),
privkey_shares.iter()
)
.enumerate()
{
let private_key_share = PrivateKeyShare::<E> {
private_key_share: *private,
};
let b = E::ScalarField::rand(rng);
let mut blinded_key_shares = private_key_share.blind(b);
blinded_key_shares.multiply_by_omega_inv(domain_inv);
private_contexts.push(PrivateDecryptionContextFast::<E> {
index,
setup_params: SetupParams {
b,
b_inv: b.inverse().unwrap(),
g,
h_inv: E::G2Prepared::from(-h.into_group()),
g_inv: E::G1Prepared::from(-g.into_group()),
h,
},
private_key_share,
public_decryption_contexts: vec![],
});
public_contexts.push(PublicDecryptionContextFast::<E> {
domain: *domain,
public_key_share: PublicKeyShare::<E> {
public_key_share: *public,
},
blinded_key_share: blinded_key_shares,
lagrange_n_0: *domain,
h_inv: E::G2Prepared::from(-h.into_group()),
});
}
for private in private_contexts.iter_mut() {
private.public_decryption_contexts = public_contexts.clone();
}
(pubkey.into(), privkey.into(), private_contexts)
}
pub fn setup_simple<E: Pairing>(
threshold: usize,
shares_num: usize,
rng: &mut impl rand::Rng,
) -> (
E::G1Affine,
E::G2Affine,
Vec<PrivateDecryptionContextSimple<E>>,
) {
assert!(shares_num >= threshold);
let g = E::G1Affine::generator();
let h = E::G2Affine::generator();
let threshold_poly =
DensePolynomial::<E::ScalarField>::rand(threshold - 1, rng);
let fft_domain =
ark_poly::GeneralEvaluationDomain::<E::ScalarField>::new(
shares_num,
)
.unwrap();
let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain);
let shares_x = fft_domain.elements().collect::<Vec<_>>();
let pubkey_shares = fast_multiexp(&evals.evals, g.into_group());
let pubkey_share = g.mul(evals.evals[0]);
debug_assert!(pubkey_shares[0] == E::G1Affine::from(pubkey_share));
let privkey_shares = fast_multiexp(&evals.evals, h.into_group());
let x = threshold_poly.coeffs[0];
let pubkey = g.mul(x);
let privkey = h.mul(x);
let secret = threshold_poly.evaluate(&E::ScalarField::zero());
debug_assert!(secret == x);
let mut private_contexts = vec![];
let mut public_contexts = vec![];
for (index, (domain, public, private)) in
izip!(shares_x.iter(), pubkey_shares.iter(), privkey_shares.iter())
.enumerate()
{
let private_key_share = PrivateKeyShare::<E> {
private_key_share: *private,
};
let b = E::ScalarField::rand(rng);
let blinded_key_share = private_key_share.blind(b);
private_contexts.push(PrivateDecryptionContextSimple::<E> {
index,
setup_params: SetupParams {
b,
b_inv: b.inverse().unwrap(),
g,
h_inv: E::G2Prepared::from(-h.into_group()),
g_inv: E::G1Prepared::from(-g.into_group()),
h,
},
private_key_share,
validator_private_key: b,
public_decryption_contexts: vec![],
});
public_contexts.push(PublicDecryptionContextSimple::<E> {
domain: *domain,
public_key_share: PublicKeyShare::<E> {
public_key_share: *public,
},
blinded_key_share,
h,
validator_public_key: h.mul(b),
});
}
for private in private_contexts.iter_mut() {
private.public_decryption_contexts = public_contexts.clone();
}
(pubkey.into(), privkey.into(), private_contexts)
}
pub fn setup_precomputed<E: Pairing>(
shares_num: usize,
rng: &mut impl rand::Rng,
) -> (
E::G1Affine,
E::G2Affine,
Vec<PrivateDecryptionContextSimple<E>>,
) {
setup_simple::<E>(shares_num, shares_num, rng)
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, ops::Mul};
use ark_bls12_381::Fr;
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
use ark_ff::Zero;
use ark_std::{test_rng, UniformRand};
use ferveo_common::{FromBytes, ToBytes};
use rand_core::RngCore;
use crate::{
refresh::{
make_random_polynomial_at, prepare_share_updates_for_recovery,
recover_share_from_updated_private_shares,
refresh_private_key_share, update_share_for_recovery,
},
test_common::{setup_simple, *},
};
type E = ark_bls12_381::Bls12_381;
type TargetField = <E as Pairing>::TargetField;
type ScalarField = <E as Pairing>::ScalarField;
fn make_shared_secret_from_contexts<E: Pairing>(
contexts: &[PrivateDecryptionContextSimple<E>],
ciphertext: &Ciphertext<E>,
aad: &[u8],
) -> SharedSecret<E> {
let decryption_shares: Vec<_> = contexts
.iter()
.map(|c| {
c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
})
.collect();
make_shared_secret(
&contexts[0].public_decryption_contexts,
&decryption_shares,
)
}
#[test]
fn ciphertext_serialization() {
let 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, _, _) = setup_fast::<E>(threshold, shares_num, rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let serialized = ciphertext.to_bytes().unwrap();
let deserialized: Ciphertext<E> =
Ciphertext::from_bytes(&serialized).unwrap();
assert_eq!(serialized, deserialized.to_bytes().unwrap())
}
fn test_ciphertext_validation_fails<E: Pairing>(
msg: &[u8],
aad: &[u8],
ciphertext: &Ciphertext<E>,
shared_secret: &SharedSecret<E>,
g_inv: &E::G1Prepared,
) {
let plaintext =
decrypt_with_shared_secret(ciphertext, aad, shared_secret, g_inv)
.unwrap();
assert_eq!(plaintext, msg);
let mut ciphertext = ciphertext.clone();
ciphertext.ciphertext[0] += 1;
assert!(decrypt_with_shared_secret(
&ciphertext,
aad,
shared_secret,
g_inv,
)
.is_err());
let aad = "bad aad".as_bytes();
assert!(decrypt_with_shared_secret(
&ciphertext,
aad,
shared_secret,
g_inv,
)
.is_err());
}
fn make_new_share_fragments<R: RngCore>(
rng: &mut R,
threshold: usize,
x_r: &Fr,
remaining_participants: &[PrivateDecryptionContextSimple<E>],
) -> Vec<PrivateKeyShare<E>> {
let domain_points = remaining_participants[0]
.public_decryption_contexts
.iter()
.map(|c| c.domain)
.collect::<Vec<_>>();
let h = remaining_participants[0].public_decryption_contexts[0].h;
let share_updates = remaining_participants
.iter()
.map(|p| {
let deltas_i = prepare_share_updates_for_recovery::<E>(
&domain_points,
&h,
x_r,
threshold,
rng,
);
(p.index, deltas_i)
})
.collect::<HashMap<_, _>>();
let new_share_fragments: Vec<_> = remaining_participants
.iter()
.map(|p| {
let updates_for_participant: Vec<_> = share_updates
.values()
.map(|updates| *updates.get(p.index).unwrap())
.collect();
update_share_for_recovery::<E>(
&p.private_key_share,
&updates_for_participant,
)
})
.collect();
new_share_fragments
}
fn make_shared_secret<E: Pairing>(
pub_contexts: &[PublicDecryptionContextSimple<E>],
decryption_shares: &[DecryptionShareSimple<E>],
) -> SharedSecret<E> {
let domain = pub_contexts.iter().map(|c| c.domain).collect::<Vec<_>>();
let lagrange_coeffs = prepare_combine_simple::<E>(&domain);
share_combine_simple::<E>(decryption_shares, &lagrange_coeffs)
}
#[test]
fn tdec_fast_variant_share_validation() {
let 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_fast::<E>(threshold, shares_num, rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let bad_aad = "bad aad".as_bytes();
assert!(contexts[0].create_share(&ciphertext, bad_aad).is_err());
}
#[test]
fn tdec_simple_variant_share_validation() {
let 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_simple::<E>(threshold, shares_num, rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let bad_aad = "bad aad".as_bytes();
assert!(contexts[0]
.create_share(&ciphertext.header().unwrap(), bad_aad)
.is_err());
}
#[test]
fn tdec_fast_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_fast::<E>(threshold, shares_num, &mut rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
.unwrap();
let g_inv = &contexts[0].setup_params.g_inv;
let mut decryption_shares: Vec<DecryptionShareFast<E>> = vec![];
for context in contexts.iter() {
decryption_shares
.push(context.create_share(&ciphertext, aad).unwrap());
}
let prepared_blinded_key_shares = prepare_combine_fast(
&contexts[0].public_decryption_contexts,
&decryption_shares,
);
let shared_secret = share_combine_fast(
&contexts[0].public_decryption_contexts,
&ciphertext,
&decryption_shares,
&prepared_blinded_key_shares,
)
.unwrap();
test_ciphertext_validation_fails(
&msg,
aad,
&ciphertext,
&shared_secret,
g_inv,
);
}
#[test]
fn tdec_simple_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_simple::<E>(threshold, shares_num, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
.unwrap();
let decryption_shares: Vec<_> = contexts
.iter()
.map(|c| {
c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
})
.take(threshold)
.collect();
let pub_contexts =
contexts[0].public_decryption_contexts[..threshold].to_vec();
let shared_secret =
make_shared_secret(&pub_contexts, &decryption_shares);
test_ciphertext_validation_fails(
&msg,
aad,
&ciphertext,
&shared_secret,
g_inv,
);
let decryption_shares = decryption_shares[..threshold - 1].to_vec();
let pub_contexts = pub_contexts[..threshold - 1].to_vec();
let shared_secret =
make_shared_secret(&pub_contexts, &decryption_shares);
let result =
decrypt_with_shared_secret(&ciphertext, aad, &shared_secret, g_inv);
assert!(result.is_err());
}
#[test]
fn tdec_precomputed_variant_e2e() {
let mut rng = &mut test_rng();
let shares_num = 16;
let msg = "my-msg".as_bytes().to_vec();
let aad: &[u8] = "my-aad".as_bytes();
let (pubkey, _, contexts) =
setup_precomputed::<E>(shares_num, &mut rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
.unwrap();
let decryption_shares: Vec<_> = contexts
.iter()
.map(|context| {
context
.create_share_precomputed(
&ciphertext.header().unwrap(),
aad,
)
.unwrap()
})
.collect();
let shared_secret = share_combine_precomputed::<E>(&decryption_shares);
test_ciphertext_validation_fails(
&msg,
aad,
&ciphertext,
&shared_secret,
g_inv,
);
let not_enough_shares = &decryption_shares[0..shares_num - 1];
let bad_shared_secret =
share_combine_precomputed::<E>(not_enough_shares);
assert!(decrypt_with_shared_secret(
&ciphertext,
aad,
&bad_shared_secret,
g_inv,
)
.is_err());
}
#[test]
fn tdec_simple_variant_share_verification() {
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_simple::<E>(threshold, shares_num, &mut rng);
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let decryption_shares: Vec<_> = contexts
.iter()
.map(|c| {
c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
})
.collect();
let pub_contexts = &contexts[0].public_decryption_contexts;
assert!(verify_decryption_shares_simple(
pub_contexts,
&ciphertext,
&decryption_shares,
));
let mut has_bad_checksum = decryption_shares[0].clone();
has_bad_checksum.validator_checksum.checksum = has_bad_checksum
.validator_checksum
.checksum
.mul(ScalarField::rand(rng))
.into_affine();
assert!(!has_bad_checksum.verify(
&pub_contexts[0].blinded_key_share.blinded_key_share,
&pub_contexts[0].validator_public_key.into_affine(),
&pub_contexts[0].h.into_group(),
&ciphertext,
));
let mut has_bad_share = decryption_shares[0].clone();
has_bad_share.decryption_share =
has_bad_share.decryption_share.mul(TargetField::rand(rng));
assert!(!has_bad_share.verify(
&pub_contexts[0].blinded_key_share.blinded_key_share,
&pub_contexts[0].validator_public_key.into_affine(),
&pub_contexts[0].h.into_group(),
&ciphertext,
));
}
#[test]
fn tdec_simple_variant_share_recovery_at_selected_point() {
let rng = &mut test_rng();
let shares_num = 16;
let threshold = shares_num * 2 / 3;
let (_, _, mut contexts) =
setup_simple::<E>(threshold, shares_num, rng);
let selected_participant = contexts.pop().unwrap();
let x_r = selected_participant
.public_decryption_contexts
.last()
.unwrap()
.domain;
let original_private_key_share = selected_participant.private_key_share;
let mut remaining_participants = contexts;
for p in &mut remaining_participants {
p.public_decryption_contexts.pop().unwrap();
}
let new_share_fragments = make_new_share_fragments(
rng,
threshold,
&x_r,
&remaining_participants,
);
let domain_points = &remaining_participants[0]
.public_decryption_contexts
.iter()
.map(|ctxt| ctxt.domain)
.collect::<Vec<_>>();
let new_private_key_share = recover_share_from_updated_private_shares(
&x_r,
domain_points,
&new_share_fragments,
);
assert_eq!(new_private_key_share, original_private_key_share);
}
#[test]
fn tdec_simple_variant_share_recovery_at_random_point() {
let 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_simple::<E>(threshold, shares_num, rng);
let g_inv = &contexts[0].setup_params.g_inv;
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let old_shared_secret =
make_shared_secret_from_contexts(&contexts, &ciphertext, aad);
let x_r = ScalarField::rand(rng);
let mut remaining_participants = contexts.clone();
remaining_participants.pop().unwrap();
for p in &mut remaining_participants {
p.public_decryption_contexts.pop().unwrap();
}
let new_share_fragments = make_new_share_fragments(
rng,
threshold,
&x_r,
&remaining_participants,
);
let domain_points = &remaining_participants[0]
.public_decryption_contexts
.iter()
.map(|ctxt| ctxt.domain)
.collect::<Vec<_>>();
let new_private_key_share = recover_share_from_updated_private_shares(
&x_r,
domain_points,
&new_share_fragments,
);
let mut decryption_shares: Vec<_> = remaining_participants
.iter()
.map(|c| {
c.create_share(&ciphertext.header().unwrap(), aad).unwrap()
})
.collect();
let new_validator_decryption_key = ScalarField::rand(rng);
decryption_shares.push(
DecryptionShareSimple::create(
&new_validator_decryption_key,
&new_private_key_share,
&ciphertext.header().unwrap(),
aad,
g_inv,
)
.unwrap(),
);
let new_shared_secret = make_shared_secret(
&remaining_participants[0].public_decryption_contexts,
&decryption_shares,
);
assert_eq!(old_shared_secret, new_shared_secret);
}
#[test]
fn tdec_simple_variant_share_refreshing() {
let 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_simple::<E>(threshold, shares_num, rng);
let g_inv = &contexts[0].setup_params.g_inv;
let pub_contexts = contexts[0].public_decryption_contexts.clone();
let ciphertext =
encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
let old_shared_secret =
make_shared_secret_from_contexts(&contexts, &ciphertext, aad);
let polynomial = make_random_polynomial_at::<E>(
threshold,
&ScalarField::zero(),
rng,
);
let new_decryption_shares: Vec<_> = contexts
.iter()
.enumerate()
.map(|(i, p)| {
let private_key_share = refresh_private_key_share::<E>(
&p.setup_params.h.into_group(),
&p.public_decryption_contexts[i].domain,
&polynomial,
&p.private_key_share,
);
DecryptionShareSimple::create(
&p.validator_private_key,
&private_key_share,
&ciphertext.header().unwrap(),
aad,
g_inv,
)
.unwrap()
})
.collect();
let new_shared_secret =
make_shared_secret(&pub_contexts, &new_decryption_shares);
assert_eq!(old_shared_secret, new_shared_secret);
}
}