Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

Update crypto deps and add a new Cipher associated type #15

Merged
merged 10 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ exclude = [ ".gitignore" ]
rustdoc-args = ["--html-in-header", "/opt/rustwide/workdir/docs/assets/rustdoc-include-katex-header.html"]

[dependencies]
aead = { version = "0.5", default-features = false, features = ["alloc", "getrandom"]}
ark-secp256k1 = { version = "0.4", default-features = false }
ark-ff = { version = "0.4", default-features = false }
ark-ec = { version = "0.4", default-features = false }
Expand All @@ -26,7 +27,7 @@ digest = { version = "0.10", default-features = false, features = ["alloc"] }
getrandom = { version = "0.2", default-features = false, features = ["js"] }
rand = { version = "0.8", default-features = false, features = ["alloc", "getrandom", "libc"] }
sha2 = { version = "0.10", default-features = false }
aes = { version = "0.7", default-features = false, features = ["ctr"] }
aes-gcm = { version = "0.10", default-features = false, features = ["aes", "alloc", "getrandom", "zeroize"] }
hkdf = { version = "0.12", default-features = false }
zeroize = { version = "1", default-features = false, features = ["alloc", "zeroize_derive"] }

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Note however that two parameters are not modular, at least in the current versio

- the hash function targeted security parameter: this crate assumes 128 bits of collision security for the ciphersuite's internal hashers. One **MUST** provide
a hasher with _at least_ 128 bits of collision security when instantiating an ICE-FROST ciphersuite.
- the secret share encryption mechanism: this part of the distributed key generation currently relies on AES128-CTR with HKDF instantiated from SHA-256.
- the secret share encryption mechanism: this part of the distributed key generation currently relies on AES128-GCM with HKDF instantiated from SHA-256.

This library also provides by default an example instantiation over the Secp256k1 curve with SHA-256, to be used in tests and benchmarks.

Expand Down
7 changes: 6 additions & 1 deletion src/ciphersuite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use core::fmt::Debug;
use core::marker::{Send, Sync};

use aead::{Aead, KeyInit};
use zeroize::Zeroize;

use ark_ec::CurveGroup;
Expand All @@ -24,9 +25,13 @@ pub trait CipherSuite: Copy + Clone + PartialEq + Eq + Debug + Send + Sync + Zer
/// A byte array of a given length for this [`CipherSuite`]'s binary hashers.
type HashOutput: AsRef<[u8]> + AsMut<[u8]> + Default;

/// The underlying hasher used to construct all random oracles of this [`CipherSuite`] .
/// The underlying hasher used to construct all random oracles of this [`CipherSuite`].
type InnerHasher: Default + Clone + Digest + DynDigest;

/// The underlying cipher used to encrypt and decrypt all `SecretShare`
/// generated during a DKG phase of this [`CipherSuite`].
type Cipher: Aead + KeyInit + Clone;

//////////////////////////////////////////////////////////////////////////////////////////////

// Required methods
Expand Down
5 changes: 3 additions & 2 deletions src/dkg/complaint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use rand::{CryptoRng, RngCore};
use sha2::Sha256;

use crate::ciphersuite::CipherSuite;
use crate::HASH_SEC_PARAM;

use ark_ec::Group;
use ark_ff::field_hashers::{DefaultFieldHasher, HashToField};
Expand Down Expand Up @@ -47,7 +48,7 @@ impl<C: CipherSuite> Complaint<C> {
let a1 = C::G::generator().mul(r);
let a2 = accused_pk.mul(r);

let hasher = <DefaultFieldHasher<Sha256, 128> as HashToField<Scalar<C>>>::new(
let hasher = <DefaultFieldHasher<Sha256, HASH_SEC_PARAM> as HashToField<Scalar<C>>>::new(
Nashtare marked this conversation as resolved.
Show resolved Hide resolved
"Complaint Context".as_bytes(),
);

Expand Down Expand Up @@ -84,7 +85,7 @@ impl<C: CipherSuite> Complaint<C> {
/// -- a1 + h.pk_i = z.g
/// -- a2 + h.k_il = z.pk_l
pub fn verify(&self, pk_i: &C::G, pk_l: &C::G) -> FrostResult<C, ()> {
let hasher = <DefaultFieldHasher<Sha256, 128> as HashToField<Scalar<C>>>::new(
let hasher = <DefaultFieldHasher<Sha256, HASH_SEC_PARAM> as HashToField<Scalar<C>>>::new(
"Complaint Context".as_bytes(),
);

Expand Down
4 changes: 2 additions & 2 deletions src/dkg/key_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2075,7 +2075,7 @@ mod test {
// Wrong decryption from nonce
{
let mut wrong_encrypted_secret_share = p1_their_encrypted_secret_shares[1].clone();
wrong_encrypted_secret_share.nonce = [42; 16];
wrong_encrypted_secret_share.nonce = [42; 12].into();
let p1_my_encrypted_secret_shares = vec![
p1_their_encrypted_secret_shares[0].clone(),
p2_their_encrypted_secret_shares[0].clone(),
Expand Down Expand Up @@ -2404,7 +2404,7 @@ mod test {

{
let wrong_encrypted_secret_share =
EncryptedSecretShare::new(1, 2, [0; 16], vec![0]);
EncryptedSecretShare::new(1, 2, [0; 12].into(), vec![0]);

let p1_my_encrypted_secret_shares = vec![
p1_their_encrypted_secret_shares[0].clone(),
Expand Down
179 changes: 141 additions & 38 deletions src/dkg/secret_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
//! and their public commitments, along with their encrypted versions
//! post Diffie-Hellman key exchange.
//!
//! This module currently only supports AES128-CTR with HKDF instantiated
//! This module currently only supports AES128-GCM with HKDF instantiated
//! from SHA-256.

use core::fmt::Debug;
use core::marker::PhantomData;

use crate::serialization::impl_serialization_traits;
use crate::utils::{Scalar, ToString, Vec};
use crate::{Error, FrostResult};

#[cfg(not(feature = "std"))]
use alloc::vec;

use crate::ciphersuite::CipherSuite;

use ark_ec::{CurveGroup, Group};
Expand All @@ -19,8 +23,7 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};

use rand::{CryptoRng, RngCore};

use aes::cipher::{generic_array::GenericArray, FromBlockCipher, NewBlockCipher, StreamCipher};
use aes::{Aes128, Aes128Ctr};
use aead::{Aead, AeadCore, Key, KeyInit, KeySizeUser, Nonce};
use hkdf::Hkdf;
use sha2::Sha256;

Expand Down Expand Up @@ -112,20 +115,107 @@ impl<C: CipherSuite> SecretShare<C> {
}

/// A secret share encrypted with a participant's public key
#[derive(Clone, Debug, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize, Zeroize)]
#[derive(Clone, Zeroize)]
pub struct EncryptedSecretShare<C: CipherSuite> {
/// The index of the share maker.
pub sender_index: u32,
/// The participant index that this secret share was calculated for.
pub receiver_index: u32,
/// The nonce to be used for decryption with AES-CTR mode.
pub nonce: [u8; 16],
/// The nonce to be used for decryption of this encrypted share.
pub nonce: Nonce<C::Cipher>,
/// The encrypted polynomial evaluation.
pub(crate) encrypted_polynomial_evaluation: Vec<u8>,
#[zeroize(skip)]
_phantom: PhantomData<C>,
}

impl<C: CipherSuite> PartialEq for EncryptedSecretShare<C> {
fn eq(&self, other: &Self) -> bool {
self.sender_index == other.sender_index
&& self.receiver_index == other.receiver_index
&& self.nonce.as_slice() == other.nonce.as_slice()
&& self.encrypted_polynomial_evaluation == other.encrypted_polynomial_evaluation
}
}

impl<C: CipherSuite> Eq for EncryptedSecretShare<C> {}

impl<C: CipherSuite> Debug for EncryptedSecretShare<C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"EncryptedSecretShare {{ sender_index: {}, receiver_index: {}, nonce: {:?}, encrypted_polynomial_evaluation: {:?} }}",
self.sender_index,
self.receiver_index,
self.nonce,
self.encrypted_polynomial_evaluation
)
}
}

// Required trait to implement `CanonicalDeserialize` below.
impl<C: CipherSuite> ark_serialize::Valid for EncryptedSecretShare<C> {
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.sender_index.check()?;
self.receiver_index.check()?;
// Collecting is not ideal for this and the `serialize_with_mode` / `deserialize_with_mode`
// implementation below, but is necessary, at least until the `GenericArray` dependency of
// the aead trait gets bumped to 1.0.
self.nonce.to_vec().check()?;
self.encrypted_polynomial_evaluation.check()
}
}

impl<C: CipherSuite> CanonicalSerialize for EncryptedSecretShare<C> {
fn serialize_with_mode<W: ark_serialize::Write>(
&self,
mut writer: W,
compress: ark_serialize::Compress,
) -> Result<(), ark_serialize::SerializationError> {
self.sender_index
.serialize_with_mode(&mut writer, compress)?;
self.receiver_index
.serialize_with_mode(&mut writer, compress)?;
self.nonce
.to_vec()
.serialize_with_mode(&mut writer, compress)?;
self.encrypted_polynomial_evaluation
.serialize_with_mode(&mut writer, compress)
}

fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
self.sender_index.serialized_size(compress)
+ self.receiver_index.serialized_size(compress)
+ self.nonce.serialized_size(compress)
+ self
.encrypted_polynomial_evaluation
.serialized_size(compress)
}
}

impl<C: CipherSuite> CanonicalDeserialize for EncryptedSecretShare<C> {
fn deserialize_with_mode<R: ark_serialize::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
let sender_index = u32::deserialize_with_mode(&mut reader, compress, validate)?;
let receiver_index = u32::deserialize_with_mode(&mut reader, compress, validate)?;
let nonce_vec = Vec::<u8>::deserialize_with_mode(&mut reader, compress, validate)?;
let nonce = Nonce::<C::Cipher>::from_exact_iter(nonce_vec)
.ok_or(ark_serialize::SerializationError::InvalidData)?;
let encrypted_polynomial_evaluation =
Vec::<u8>::deserialize_with_mode(&mut reader, compress, validate)?;

Ok(EncryptedSecretShare::<C>::new(
sender_index,
receiver_index,
nonce,
encrypted_polynomial_evaluation,
))
}
}

impl_serialization_traits!(EncryptedSecretShare<CipherSuite>);

impl<C: CipherSuite> Drop for EncryptedSecretShare<C> {
Expand All @@ -139,7 +229,7 @@ impl<C: CipherSuite> EncryptedSecretShare<C> {
pub fn new(
sender_index: u32,
receiver_index: u32,
nonce: [u8; 16],
nonce: Nonce<C::Cipher>,
encrypted_polynomial_evaluation: Vec<u8>,
) -> Self {
Self {
Expand Down Expand Up @@ -192,60 +282,73 @@ impl<C: CipherSuite> VerifiableSecretSharingCommitment<C> {
}
}

/// Encrypt a `SecretShare` given an initial slice of bytes.
///
/// This will perform an HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
/// expansion over the Initial Key Material passed as input. The obtained Output Key
/// Material will then be passed to this `Ciphersuite`'s cipher along with a random
/// nonce to encrypt the private polynomial evaluation corresponding to this secret share.
pub(crate) fn encrypt_share<C: CipherSuite>(
share: &SecretShare<C>,
aes_key: &[u8],
mut rng: impl RngCore + CryptoRng,
initial_key_bytes: &[u8],
rng: impl RngCore + CryptoRng,
) -> FrostResult<C, EncryptedSecretShare<C>> {
let hkdf = Hkdf::<Sha256>::new(None, aes_key);
let mut final_aes_key = [0u8; 16];
hkdf.expand(&[], &mut final_aes_key)
let hkdf = Hkdf::<Sha256>::new(None, initial_key_bytes);
let mut final_aead_key = vec![0u8; <C::Cipher as KeySizeUser>::key_size()];
hkdf.expand(&[], &mut final_aead_key)
.map_err(|_| Error::Custom("KDF expansion failed unexpectedly".to_string()))?;

let mut nonce_array = [0u8; 16];
rng.fill_bytes(&mut nonce_array);
let key = Key::<C::Cipher>::from_slice(&final_aead_key); // This cannot panic.
let cipher = C::Cipher::new(key);

let final_aes_key = GenericArray::from_slice(&final_aes_key);
let nonce = GenericArray::from_slice(&nonce_array);
let cipher = Aes128::new(final_aes_key);
let mut cipher = Aes128Ctr::from_block_cipher(cipher, nonce);
let nonce = C::Cipher::generate_nonce(rng);

let mut share_bytes = Vec::with_capacity(share.polynomial_evaluation.compressed_size());
share
.polynomial_evaluation
.serialize_compressed(&mut share_bytes)
.map_err(|_| Error::CompressionError)?;
cipher.apply_keystream(&mut share_bytes);

let encrypted_polynomial_evaluation = cipher
.encrypt(&nonce, share_bytes.as_ref())
.map_err(|_| Error::EncryptionError)?;

Ok(EncryptedSecretShare::<C> {
sender_index: share.sender_index,
receiver_index: share.receiver_index,
nonce: nonce_array,
encrypted_polynomial_evaluation: share_bytes,
nonce,
encrypted_polynomial_evaluation,
_phantom: PhantomData,
})
}

/// Decrypt an `EncryptedSecretShare` given an initial slice of bytes.
///
/// This will perform an HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
/// expansion over the Initial Key Material passed as input. The obtained Output Key
/// Material will then be passed to this `Ciphersuite`'s cipher along with the associated
/// nonce used to obtain this `EncryptedSecretShare` to decrypt it.
pub(crate) fn decrypt_share<C: CipherSuite>(
encrypted_share: &EncryptedSecretShare<C>,
aes_key: &[u8],
initial_key_bytes: &[u8],
) -> FrostResult<C, SecretShare<C>> {
let hkdf = Hkdf::<Sha256>::new(None, aes_key);
let mut final_aes_key = [0u8; 16];
hkdf.expand(&[], &mut final_aes_key)
.expect("KDF expansion failed unexpectedly");

let final_aes_key = GenericArray::from_slice(&final_aes_key);
let hkdf = Hkdf::<Sha256>::new(None, initial_key_bytes);
let mut final_aead_key = vec![0u8; <C::Cipher as KeySizeUser>::key_size()];
hkdf.expand(&[], &mut final_aead_key)
.map_err(|_| Error::Custom("KDF expansion failed unexpectedly".to_string()))?;

let nonce = GenericArray::from_slice(&encrypted_share.nonce);
let cipher = Aes128::new(final_aes_key);
let mut cipher = Aes128Ctr::from_block_cipher(cipher, nonce);
let key = Key::<C::Cipher>::from_slice(&final_aead_key); // This cannot panic.
let cipher = C::Cipher::new(key);

let mut bytes = encrypted_share.encrypted_polynomial_evaluation.clone();
cipher.apply_keystream(&mut bytes);
let bytes = cipher
.decrypt(
&encrypted_share.nonce,
encrypted_share.encrypted_polynomial_evaluation.as_ref(),
)
.map_err(|_| Error::DecryptionError)?;

let evaluation =
Scalar::<C>::deserialize_compressed(&bytes[..]).map_err(|_| Error::DecryptionError)?;
Scalar::<C>::deserialize_compressed(&bytes[..]).map_err(|_| Error::DecompressionError)?;

Ok(SecretShare {
sender_index: encrypted_share.sender_index,
Expand Down Expand Up @@ -282,14 +385,14 @@ mod test {
}

for _ in 0..100 {
let mut nonce = [0u8; 16];
let mut encrypted_polynomial_evaluation = vec![0u8; 16];
let mut nonce = [0u8; 12];
let mut encrypted_polynomial_evaluation = vec![0u8; 12];
rng.fill_bytes(&mut nonce);
rng.fill_bytes(&mut encrypted_polynomial_evaluation);
let encrypted_secret_share = EncryptedSecretShare::<Secp256k1Sha256>::new(
rng.next_u32(),
rng.next_u32(),
nonce,
nonce.into(),
encrypted_polynomial_evaluation,
);
let mut bytes = Vec::with_capacity(encrypted_secret_share.compressed_size());
Expand All @@ -298,7 +401,7 @@ mod test {
.unwrap();
assert_eq!(
encrypted_secret_share,
EncryptedSecretShare::deserialize_compressed(&bytes[..]).unwrap()
EncryptedSecretShare::deserialize_compressed(&bytes[..]).unwrap(),
);
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub enum Error<C: CipherSuite> {
DecompressionError,
/// Encrypted secret share decryption failure
DecryptionError,
/// Secret share encryption failure
EncryptionError,
/// Secret share verification failure
ShareVerificationError,
/// Complaint verification failure
Expand Down Expand Up @@ -73,6 +75,9 @@ impl<C: CipherSuite> core::fmt::Display for Error<C> {
Error::DecryptionError => {
write!(f, "Could not decrypt encrypted share.")
}
Error::EncryptionError => {
write!(f, "Could not encrypt secret share.")
}
Error::ShareVerificationError => {
write!(f, "The secret share is not correct.")
}
Expand Down
Loading
Loading