aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/error.rs13
-rw-r--r--src/hibe/bbg.rs320
-rw-r--r--src/hibe/mod.rs72
-rw-r--r--src/kem.rs489
-rw-r--r--src/lib.rs43
5 files changed, 937 insertions, 0 deletions
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..155a388
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,13 @@
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("The supplied identity was too long")]
+ IdentityTooLong,
+ #[error("The supplied identity had no elements")]
+ EmptyIdentity,
+ #[error("The supplied ciphertext was malforemd")]
+ MalformedCiphertext,
+}
+
+pub type Result<V, E=Error> = std::result::Result<V, E>;
diff --git a/src/hibe/bbg.rs b/src/hibe/bbg.rs
new file mode 100644
index 0000000..dc2a129
--- /dev/null
+++ b/src/hibe/bbg.rs
@@ -0,0 +1,320 @@
+use std::iter;
+
+use super::{Hibe, HibeCrypt, HibeKem};
+use crate::error::{Error, Result};
+
+use bls12_381_plus::{
+ ff::Field, group::Group, pairing, G1Affine, G2Affine, G2Projective, Gt, Scalar,
+};
+use rand::Rng;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct BonehBoyenGoh {
+ max_depth: usize,
+}
+
+impl BonehBoyenGoh {
+ pub fn new(max_depth: usize) -> Self {
+ Self { max_depth }
+ }
+
+ pub fn max_depth(&self) -> usize {
+ self.max_depth
+ }
+}
+
+impl Hibe for BonehBoyenGoh {
+ type PrivateKey = (G2Affine, G1Affine, Vec<G2Affine>);
+ type MasterKey = G2Affine;
+ type PublicKey = (G1Affine, G1Affine, G2Affine, G2Affine, Vec<G2Affine>);
+ type Identity = Scalar;
+
+ fn setup<R: Rng>(&self, mut rng: R) -> Result<(Self::PublicKey, Self::MasterKey)> {
+ let g = G1Affine::generator();
+ let alpha = Scalar::random(&mut rng);
+ let g1 = g * alpha;
+ let g2 = G2Projective::random(&mut rng);
+ let g3 = G2Projective::random(&mut rng);
+ let hs = (0..self.max_depth())
+ .map(|_| G2Projective::random(&mut rng))
+ .map(Into::into)
+ .collect();
+ Ok((
+ (g, g1.into(), g2.into(), g3.into(), hs),
+ (g2 * alpha).into(),
+ ))
+ }
+
+ fn generate_key<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ master_key: &Self::MasterKey,
+ identity: &[Self::Identity],
+ ) -> Result<Self::PrivateKey> {
+ if identity.len() > self.max_depth() {
+ return Err(Error::IdentityTooLong);
+ }
+
+ let r = Scalar::random(rng);
+ Ok((
+ (master_key
+ + (public_key
+ .4
+ .iter()
+ .zip(identity)
+ .map(|(h, i)| h * i)
+ .sum::<G2Projective>()
+ + public_key.3)
+ * r)
+ .into(),
+ (public_key.0 * r).into(),
+ public_key.4[identity.len()..]
+ .iter()
+ .map(|h| (h * r).into())
+ .collect(),
+ ))
+ }
+
+ fn derive_key<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ parent_key: &Self::PrivateKey,
+ parent_name: &[Self::Identity],
+ child: &Self::Identity,
+ ) -> Result<Self::PrivateKey> {
+ if parent_name.len() > self.max_depth() - 1 {
+ return Err(Error::IdentityTooLong);
+ }
+
+ let t = Scalar::random(rng);
+ Ok((
+ (parent_key.0
+ + parent_key.2[0] * child
+ + (public_key
+ .4
+ .iter()
+ .zip(parent_name.iter().chain(iter::once(child)))
+ .map(|(h, i)| h * i)
+ .sum::<G2Projective>()
+ + public_key.3)
+ * t)
+ .into(),
+ (parent_key.1 + public_key.0 * t).into(),
+ parent_key.2[1..]
+ .iter()
+ .zip(public_key.4[parent_name.len() + 1..].iter())
+ .map(|(b, h)| b + h * t)
+ .map(Into::into)
+ .collect(),
+ ))
+ }
+}
+
+impl HibeCrypt for BonehBoyenGoh {
+ type Message = Gt;
+
+ type Ciphertext = (Gt, G1Affine, G2Affine);
+
+ fn encrypt<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ identity: &[Self::Identity],
+ message: &Self::Message,
+ ) -> Result<Self::Ciphertext> {
+ if identity.len() > self.max_depth() {
+ return Err(Error::IdentityTooLong);
+ }
+
+ let s = Scalar::random(rng);
+ Ok((
+ pairing(&public_key.1, &public_key.2) * s + message,
+ (public_key.0 * s).into(),
+ ((public_key
+ .4
+ .iter()
+ .zip(identity.iter())
+ .map(|(h, i)| h * i)
+ .sum::<G2Projective>()
+ + public_key.3)
+ * s)
+ .into(),
+ ))
+ }
+
+ fn decrypt(
+ &self,
+ _: &Self::PublicKey,
+ key: &Self::PrivateKey,
+ ciphertext: &Self::Ciphertext,
+ ) -> Result<Self::Message> {
+ let (a, b, c) = ciphertext;
+ Ok(a + pairing(&key.1, c) - pairing(b, &key.0))
+ }
+}
+
+impl HibeKem for BonehBoyenGoh {
+ type Key = Gt;
+
+ type EncapsulatedKey = (G1Affine, G2Affine);
+
+ fn encapsulate<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ identity: &[Self::Identity],
+ ) -> Result<(Self::Key, Self::EncapsulatedKey)> {
+ if identity.len() > self.max_depth() {
+ return Err(Error::IdentityTooLong);
+ }
+
+ let s = Scalar::random(rng);
+ Ok((
+ pairing(&public_key.1, &public_key.2) * s,
+ (
+ (public_key.0 * s).into(),
+ ((public_key
+ .4
+ .iter()
+ .zip(identity.iter())
+ .map(|(h, i)| h * i)
+ .sum::<G2Projective>()
+ + public_key.3)
+ * s)
+ .into(),
+ ),
+ ))
+ }
+
+ fn decapsulate(
+ &self,
+ _: &Self::PublicKey,
+ key: &Self::PrivateKey,
+ encapsulation: &Self::EncapsulatedKey,
+ ) -> Result<Self::Key> {
+ let (b, c) = encapsulation;
+ Ok(-pairing(&key.1, c) + pairing(b, &key.0))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn encrypt_decrypt_empty_identity() {
+ let mut rng = rand::thread_rng();
+ let bbg = BonehBoyenGoh::new(5);
+ let (public_key, master_key) = bbg.setup(&mut rng).unwrap();
+ let identity = &[];
+ let secret_key = bbg
+ .generate_key(&mut rng, &public_key, &master_key, identity.as_slice())
+ .unwrap();
+ let message = Gt::generator() * Scalar::from(4u32);
+ let ciphertext = bbg
+ .encrypt(&mut rng, &public_key, identity.as_slice(), &message)
+ .unwrap();
+ let decryption = bbg.decrypt(&public_key, &secret_key, &ciphertext).unwrap();
+ assert_eq!(message, decryption);
+ }
+
+ #[test]
+ fn encrypt_decrypt_keygen() {
+ let mut rng = rand::thread_rng();
+ let bbg = BonehBoyenGoh::new(5);
+ let (public_key, master_key) = bbg.setup(&mut rng).unwrap();
+ let identity = &[Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)];
+ let secret_key = bbg
+ .generate_key(&mut rng, &public_key, &master_key, identity.as_slice())
+ .unwrap();
+ let message = Gt::generator() * Scalar::from(4u32);
+ let ciphertext = bbg
+ .encrypt(&mut rng, &public_key, identity.as_slice(), &message)
+ .unwrap();
+ let decryption = bbg.decrypt(&public_key, &secret_key, &ciphertext).unwrap();
+ assert_eq!(message, decryption);
+ }
+
+ #[test]
+ fn encrypt_decrypt_derived() {
+ let mut rng = rand::thread_rng();
+ let bbg = BonehBoyenGoh::new(5);
+ let (public_key, master_key) = bbg.setup(&mut rng).unwrap();
+ let identity = &[Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)];
+ let secret_key_0 = bbg
+ .generate_key(&mut rng, &public_key, &master_key, &[])
+ .unwrap();
+ let secret_key_1 = bbg
+ .derive_key(
+ &mut rng,
+ &public_key,
+ &secret_key_0,
+ &identity[..0],
+ &identity[0],
+ )
+ .unwrap();
+ let secret_key_2 = bbg
+ .derive_key(
+ &mut rng,
+ &public_key,
+ &secret_key_1,
+ &identity[..1],
+ &identity[1],
+ )
+ .unwrap();
+ let secret_key_3 = bbg
+ .derive_key(
+ &mut rng,
+ &public_key,
+ &secret_key_2,
+ &identity[..2],
+ &identity[2],
+ )
+ .unwrap();
+ let message = Gt::generator() * Scalar::from(4u32);
+ let ciphertext = bbg
+ .encrypt(&mut rng, &public_key, identity.as_slice(), &message)
+ .unwrap();
+ let decryption = bbg
+ .decrypt(&public_key, &secret_key_3, &ciphertext)
+ .unwrap();
+ assert_eq!(message, decryption);
+ }
+
+ #[test]
+ fn encrypt_decrypt_wrong_id() {
+ let mut rng = rand::thread_rng();
+ let bbg = BonehBoyenGoh::new(5);
+ let (public_key, master_key) = bbg.setup(&mut rng).unwrap();
+ let identity = &[Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)];
+ let secret_key = bbg
+ .generate_key(&mut rng, &public_key, &master_key, identity.as_slice())
+ .unwrap();
+ let message = Gt::generator() * Scalar::from(4u32);
+ let ciphertext = bbg
+ .encrypt(&mut rng, &public_key, &identity[..1], &message)
+ .unwrap();
+ let decryption = bbg.decrypt(&public_key, &secret_key, &ciphertext).unwrap();
+ assert_ne!(message, decryption);
+ }
+
+ #[test]
+ fn encapsulate_decapsulate_keygen() {
+ let mut rng = rand::thread_rng();
+ let bbg = BonehBoyenGoh::new(5);
+ let (public_key, master_key) = bbg.setup(&mut rng).unwrap();
+ let identity = &[Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)];
+ let secret_key = bbg
+ .generate_key(&mut rng, &public_key, &master_key, identity.as_slice())
+ .unwrap();
+ let (generated_key, encapsulated_key) = bbg
+ .encapsulate(&mut rng, &public_key, identity.as_slice())
+ .unwrap();
+ let decapsulated_key = bbg
+ .decapsulate(&public_key, &secret_key, &encapsulated_key)
+ .unwrap();
+ assert_eq!(generated_key, decapsulated_key);
+ }
+}
diff --git a/src/hibe/mod.rs b/src/hibe/mod.rs
new file mode 100644
index 0000000..20d83a6
--- /dev/null
+++ b/src/hibe/mod.rs
@@ -0,0 +1,72 @@
+use super::error::Result;
+
+use rand::Rng;
+
+mod bbg;
+pub use self::bbg::BonehBoyenGoh;
+
+pub trait Hibe {
+ type PrivateKey;
+ type MasterKey;
+ type PublicKey;
+ type Identity;
+
+ fn setup<R: Rng>(&self, rng: R) -> Result<(Self::PublicKey, Self::MasterKey)>;
+
+ fn generate_key<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ master_key: &Self::MasterKey,
+ identity: &[Self::Identity],
+ ) -> Result<Self::PrivateKey>;
+
+ fn derive_key<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ parent_key: &Self::PrivateKey,
+ parent_name: &[Self::Identity],
+ child: &Self::Identity,
+ ) -> Result<Self::PrivateKey>;
+}
+
+pub trait HibeCrypt: Hibe {
+ type Message;
+ type Ciphertext;
+
+ fn encrypt<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ identity: &[Self::Identity],
+ message: &Self::Message,
+ ) -> Result<Self::Ciphertext>;
+
+ fn decrypt(
+ &self,
+ public_key: &Self::PublicKey,
+ key: &Self::PrivateKey,
+ ciphertext: &Self::Ciphertext,
+ ) -> Result<Self::Message>;
+}
+
+pub trait HibeKem: Hibe {
+ type Key;
+ type EncapsulatedKey;
+
+ fn encapsulate<R: Rng>(
+ &self,
+ rng: R,
+ public_key: &Self::PublicKey,
+ identity: &[Self::Identity],
+ ) -> Result<(Self::Key, Self::EncapsulatedKey)>;
+
+ fn decapsulate(
+ &self,
+ public_key: &Self::PublicKey,
+ key: &Self::PrivateKey,
+ encapsulation: &Self::EncapsulatedKey,
+ ) -> Result<Self::Key>;
+}
+
diff --git a/src/kem.rs b/src/kem.rs
new file mode 100644
index 0000000..a8261b6
--- /dev/null
+++ b/src/kem.rs
@@ -0,0 +1,489 @@
+//! High-level wrapper around HIBE operations.
+//!
+//! Usually, people don't communicate by sending each other group elements. Therefore, we provide
+//! this opinionated and easier-to-use wrapper over the raw HIBE operations.
+//!
+//! The main struct is [`HybridKem`], which wraps a [`Hibe`] to provide high-level operations. The
+//! main differences include:
+//!
+//! * Generated keys (from [`HibeKem`]) are hashed to `[u8; 16]` (128 Bit) using SHA3 to make it
+//! easy to use them in other cryptographic primitives.
+//! * Encryption and decryption (from [`HibeCrypt`][super::hibe::HibeCrypt]) is realized by using
+//! hybrid encryption, using the KEM and AES encryption on top. This allows you to easily encrypt
+//! byte sequences instead of group elements.
+//! * Identities are mapped through a [`Mapper`], which makes it easier to specify identities at
+//! the call-site. You can for example provide a mapper that takes IP addresses as input, and
+//! outputs the correct "raw" identity.
+//! * The types are wrapped in proper opaque structs instead of being type aliases. This makes it
+//! easier to implement new methods on those types or customize their behaviour (for example, by
+//! providing an easier-to-use [`Debug`] implementation).
+//! * The methods in this module are restricted to [`CryptoRng`] random generators to enforce the
+//! use of cryptographically secure algorithms.
+//!
+//! To provide easier usability, this implementation is hardwired to use [`BonehBoyenGoh`] as the
+//! underlying HIBE. This allows us to not fiddle around with too many generic parameters and
+//! generic bounds. Maybe this will change in the future.
+//!
+//! As a default mapper, [`HashMapper`] is provided, which uses the existing [`std::hash::Hash`]
+//! implementation on types to hash to a identity. Note that this may lead to collisions, like with
+//! any hash — but they are expected to by unlikely, as the underlying hash is SHA3-256.
+use super::{
+ error::{Error, Result},
+ hibe::{BonehBoyenGoh, Hibe, HibeKem},
+ ByteAccess, Mapper,
+};
+
+use std::{
+ fmt::{self, Debug},
+ hash::{Hash, Hasher},
+};
+
+use aes::cipher::{KeyIvInit, StreamCipher};
+use bls12_381_plus::{Gt, Scalar};
+use rand::{CryptoRng, Rng};
+use serde::{Deserialize, Serialize};
+use sha3::{Digest, Sha3_256};
+
+type AesCtr = ctr::Ctr64LE<aes::Aes128>;
+static IV: [u8; 16] = [0; 16];
+
+/// Represents a public key.
+///
+/// In the context of HIBE, this is the "master public key", sometimes also called the "public
+/// parameters". The key does not represent the public key for a single identity, but rather the
+/// global public key. The encryption functionality then takes the identity as an additional
+/// parameter.
+///
+/// You mainly want to pass this object around (e.g. to [`HybridKem::encrypt`]) without caring
+/// about its internals. You can however serialize and deserialize a key to save or transmit it.
+///
+/// Note that the debug output does not output all inner bytes and instead outputs a small
+/// fingerprint only. This makes it easier to use the debug output, as the actual value has too
+/// many bytes to show nicely.
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct PublicKey(<BonehBoyenGoh as Hibe>::PublicKey);
+
+impl From<<BonehBoyenGoh as Hibe>::PublicKey> for PublicKey {
+ fn from(value: <BonehBoyenGoh as Hibe>::PublicKey) -> Self {
+ Self(value)
+ }
+}
+
+impl ByteAccess for PublicKey {
+ fn bytes(&self) -> Vec<u8> {
+ bincode::serialize(&self.0).unwrap()
+ }
+}
+
+impl Debug for PublicKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("PublicKey")
+ .field(&self.fingerprint())
+ .finish()
+ }
+}
+
+/// Represents a the master secret key.
+///
+/// The master secret key allows the holder to generate secret keys for any identity.
+///
+/// You mainly want to pass this object around (e.g. to [`HybridKem::generate_key`]) without caring
+/// about its internals. You can however serialize and deserialize a key to save or transmit it.
+///
+/// Note that the debug output does not output all inner bytes and instead outputs a small
+/// fingerprint only. This makes it easier to use the debug output, as the actual value has too
+/// many bytes to show nicely.
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct MasterKey(<BonehBoyenGoh as Hibe>::MasterKey);
+
+impl From<<BonehBoyenGoh as Hibe>::MasterKey> for MasterKey {
+ fn from(value: <BonehBoyenGoh as Hibe>::MasterKey) -> Self {
+ Self(value)
+ }
+}
+
+impl ByteAccess for MasterKey {
+ fn bytes(&self) -> Vec<u8> {
+ bincode::serialize(&self.0).unwrap()
+ }
+}
+
+impl Debug for MasterKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("MasterKey")
+ .field(&self.fingerprint())
+ .finish()
+ }
+}
+
+/// Represents an identity's secret key.
+///
+/// This private key allows the holder to decrypt ciphertexts for the identity it belongs to.
+///
+/// You mainly want to pass this object around (e.g. to [`HybridKem::decrypt`]) without caring
+/// about its internals. You can however serialize and deserialize a key to save or transmit it.
+///
+/// Note that the debug output does not output all inner bytes and instead outputs a small
+/// fingerprint only. This makes it easier to use the debug output, as the actual value has too
+/// many bytes to show nicely.
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct PrivateKey(<BonehBoyenGoh as Hibe>::PrivateKey);
+
+impl From<<BonehBoyenGoh as Hibe>::PrivateKey> for PrivateKey {
+ fn from(value: <BonehBoyenGoh as Hibe>::PrivateKey) -> Self {
+ Self(value)
+ }
+}
+
+impl ByteAccess for PrivateKey {
+ fn bytes(&self) -> Vec<u8> {
+ bincode::serialize(&self.0).unwrap()
+ }
+}
+
+impl Debug for PrivateKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("PrivateKey")
+ .field(&self.fingerprint())
+ .finish()
+ }
+}
+
+/// Represents an encapsulated key.
+///
+/// An encapsulated key is the precursor to a shared secret: by applying their secret key to it, an
+/// identity can generate the same key that has been embedded by the creator of the encapsulation.
+///
+/// You mainly want to pass this object around (e.g. to [`HybridKem::decapsulate`]) without caring
+/// about its internals. You can however serialize and deserialize a key to save or transmit it.
+///
+/// Note that the debug output does not output all inner bytes and instead outputs a small
+/// fingerprint only. This makes it easier to use the debug output, as the actual value has too
+/// many bytes to show nicely.
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct EncapsulatedKey(<BonehBoyenGoh as HibeKem>::EncapsulatedKey);
+
+impl From<<BonehBoyenGoh as HibeKem>::EncapsulatedKey> for EncapsulatedKey {
+ fn from(value: <BonehBoyenGoh as HibeKem>::EncapsulatedKey) -> Self {
+ Self(value)
+ }
+}
+
+impl ByteAccess for EncapsulatedKey {
+ fn bytes(&self) -> Vec<u8> {
+ bincode::serialize(&self.0).unwrap()
+ }
+}
+
+impl Debug for EncapsulatedKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("EncapsulatedKey")
+ .field(&self.fingerprint())
+ .finish()
+ }
+}
+
+fn hash_from_curve(element: Gt) -> [u8; 16] {
+ let mut result = [0; 16];
+ result.copy_from_slice(&Sha3_256::digest(element.to_bytes())[..16]);
+ result
+}
+
+/// High-level implementation of HIBE operations.
+///
+/// This struct internally uses the [`BonehBoyenGoh`]-HIBE to provide high-level key-encapsulation
+/// and encryption. In addition, this struct keeps a [`Mapper`] around to do the mapping of
+/// high-level identities to low-level identities.
+///
+/// For more information about the differences to [`Hibe`], see the [module-level][self] documentation.
+#[derive(Clone, Debug)]
+pub struct HybridKem<M> {
+ hibe: BonehBoyenGoh,
+ mapper: M,
+}
+
+impl HybridKem<HashMapper> {
+ /// Create a new [`HybridKem`] using the [`HashMapper`] mapper.
+ ///
+ /// Parameters:
+ ///
+ /// * `max_depth` - Maximum depth that the hierarchy should support.
+ pub fn new(max_depth: usize) -> HybridKem<HashMapper> {
+ Self::new_with_mapper(max_depth, HashMapper)
+ }
+}
+
+impl<M> HybridKem<M> {
+ /// Create a new [`HybridKem`] with the given [`Mapper`].
+ ///
+ /// Parameters:
+ ///
+ /// * `max_depth` - Maximum depth that the hierarchy should support.
+ /// * `mapper` - The mapper to use for identity mapping.
+ pub fn new_with_mapper(max_depth: usize, mapper: M) -> HybridKem<M> {
+ let hibe = BonehBoyenGoh::new(max_depth);
+ Self { hibe, mapper }
+ }
+
+ /// Wraps an existing [`BonehBoyenGoh`] HIBE.
+ ///
+ /// This will result in the [`HybridKem`] supporting the same hierarchy depth that the wrapped
+ /// HIBE supports.
+ ///
+ /// Parameters:
+ ///
+ /// * `hibe` - The inner HIBE to wrap.
+ /// * `mapper` - The mapper to use for identity mapping.
+ pub fn wrap(hibe: BonehBoyenGoh, mapper: M) -> HybridKem<M> {
+ Self { hibe, mapper }
+ }
+
+ /// Sets up the system parameters.
+ ///
+ /// This operation will return the master public key and the master secret key.
+ ///
+ /// Parameters:
+ ///
+ /// * `rng` - The randomness to use.
+ pub fn setup<R: Rng + CryptoRng>(&self, rng: R) -> Result<(PublicKey, MasterKey)> {
+ let (public_key, master_key) = self.hibe.setup(rng)?;
+ Ok((public_key.into(), master_key.into()))
+ }
+
+ /// Generates the secret key for a user using the master secret key.
+ ///
+ /// Parameters:
+ ///
+ /// * `rng` - The randomness to use.
+ /// * `public_key` - The public key of the system.
+ /// * `master_key` - The master secret key.
+ /// * `identity` - The identity for which to generate the key.
+ pub fn generate_key<I, R: Rng + CryptoRng>(
+ &self,
+ rng: R,
+ public_key: &PublicKey,
+ master_key: &MasterKey,
+ identity: I,
+ ) -> Result<PrivateKey>
+ where
+ M: Mapper<I, <BonehBoyenGoh as Hibe>::Identity>,
+ {
+ let identity = self.mapper.map_identity(identity)?;
+ let private_key = self
+ .hibe
+ .generate_key(rng, &public_key.0, &master_key.0, &identity)?;
+ Ok(private_key.into())
+ }
+
+ /// Derives the secret key for an identity given the secret key of its parent.
+ ///
+ /// If the given `parent_key` does not actually belong to the parent of `identity`, the
+ /// resulting key will be wrong.
+ ///
+ /// Parameters:
+ ///
+ /// * `rng` - The randomness to use.
+ /// * `public_key` - The public key of the system.
+ /// * `parent_key` - The key of the parent identity.
+ /// * `identity` - The identity for which to generate the key.
+ pub fn derive_key<I, R: Rng + CryptoRng>(
+ &self,
+ rng: R,
+ public_key: &PublicKey,
+ parent_key: &PrivateKey,
+ identity: I,
+ ) -> Result<PrivateKey>
+ where
+ M: Mapper<I, <BonehBoyenGoh as Hibe>::Identity>,
+ {
+ let identity = self.mapper.map_identity(identity)?;
+ let Some((child, parent)) = identity.split_last() else {
+ return Err(Error::EmptyIdentity);
+ };
+ let private_key = self
+ .hibe
+ .derive_key(rng, &public_key.0, &parent_key.0, parent, child)?;
+ Ok(private_key.into())
+ }
+
+ /// Encapsulate a key for the given identity.
+ ///
+ /// This returns the key and its encapsulation.
+ ///
+ /// Parameters:
+ ///
+ /// * `rng` - The randomness to use.
+ /// * `public_key` - The public key of the system.
+ /// * `identity` - The identity for which to generate the key.
+ pub fn encapsulate<I, R: Rng + CryptoRng>(
+ &self,
+ rng: R,
+ public_key: &PublicKey,
+ identity: I,
+ ) -> Result<([u8; 16], EncapsulatedKey)>
+ where
+ M: Mapper<I, <BonehBoyenGoh as Hibe>::Identity>,
+ {
+ let identity = self.mapper.map_identity(identity)?;
+ let (key, encapsulation) = self.hibe.encapsulate(rng, &public_key.0, &identity)?;
+ Ok((hash_from_curve(key), encapsulation.into()))
+ }
+
+ /// Decapsulate the given key.
+ ///
+ /// If the correct secret key is given, this will return the same key that the corresponding
+ /// [`HybridKem::encapsulate`] call also returned.
+ ///
+ /// Parameters:
+ ///
+ /// * `public_key` - The public key of the system.
+ /// * `key` - The private key of the receiving identity.
+ /// * `encapsulation` - The encapsulation of the key.
+ pub fn decapsulate(
+ &self,
+ public_key: &PublicKey,
+ key: &PrivateKey,
+ encapsulation: &EncapsulatedKey,
+ ) -> Result<[u8; 16]> {
+ let key = self
+ .hibe
+ .decapsulate(&public_key.0, &key.0, &encapsulation.0)?;
+ Ok(hash_from_curve(key))
+ }
+
+ /// Encrypt the given byte sequence for the given identity.
+ ///
+ /// This internally uses a hybrid encryption where the key is encapsulated by the KEM, and the
+ /// payload is then encrypted symetrically with AES (counter mode).
+ ///
+ /// Note that the resulting ciphertext is longer than the payload, as some space is needed for
+ /// the encapsulated key. The key is automatically prepended to the encrypted payload.
+ ///
+ /// Parameters:
+ ///
+ /// * `rng` - The randomness to use.
+ /// * `public_key` - The public key of the system.
+ /// * `identity` - The identity for which to encrypt the payload.
+ /// * `payload` - Payload to encrypt.
+ pub fn encrypt<I, R: Rng + CryptoRng>(
+ &self,
+ rng: R,
+ public_key: &PublicKey,
+ identity: I,
+ payload: &[u8],
+ ) -> Result<Vec<u8>>
+ where
+ M: Mapper<I, <BonehBoyenGoh as Hibe>::Identity>,
+ {
+ let (key, encapsulation) = self.encapsulate(rng, public_key, identity)?;
+ let mut buffer = Vec::from(payload);
+ let mut cipher = AesCtr::new(&key.into(), &IV.into());
+ cipher.apply_keystream(&mut buffer);
+ Ok(bincode::serialize(&(encapsulation, buffer)).expect("Serialization failed"))
+ }
+
+ /// Decrypt the given ciphertext.
+ ///
+ /// Returns the payload.
+ ///
+ /// Parameters:
+ ///
+ /// * `public_key` - The public key of the system.
+ /// * `key` - The private key of the receiving identity.
+ /// * `ciphertext` - The ciphertext, as previously returned by [`HybridKem::encrypt`].
+ pub fn decrypt(
+ &self,
+ public_key: &PublicKey,
+ key: &PrivateKey,
+ ciphertext: &[u8],
+ ) -> Result<Vec<u8>> {
+ let (encapsulation, mut buffer): (EncapsulatedKey, Vec<u8>) =
+ bincode::deserialize(ciphertext).map_err(|_| Error::MalformedCiphertext)?;
+ let key = self.decapsulate(public_key, key, &encapsulation)?;
+ let mut cipher = AesCtr::new(&key.into(), &IV.into());
+ cipher.apply_keystream(&mut buffer);
+ Ok(buffer)
+ }
+}
+
+#[derive(Default)]
+struct Sha3Hasher(Sha3_256);
+
+impl Sha3Hasher {
+ fn hash_to_scalar(self) -> Scalar {
+ let mut bytes = [0; 48];
+ bytes[..32].copy_from_slice(&self.0.finalize());
+ Scalar::from_okm(&bytes)
+ }
+
+ fn hash<H: Hash>(element: &H) -> Scalar {
+ let mut hasher = Sha3Hasher::default();
+ element.hash(&mut hasher);
+ hasher.hash_to_scalar()
+ }
+}
+
+impl Hasher for Sha3Hasher {
+ fn finish(&self) -> u64 {
+ u64::from_be_bytes(self.0.clone().finalize()[..8].try_into().unwrap())
+ }
+
+ fn write(&mut self, bytes: &[u8]) {
+ self.0.update(bytes);
+ }
+}
+
+/// A [`Mapper`] that works for all types implementing [`std::hash::Hash`].
+///
+/// This mapper uses the hash implementation to hash objects to [`Scalar`]s, the underlying
+/// identity element for [`HybridKem`]. Internally, a SHA3-256 instance is used to provide
+/// consistent hashing and collision resistance.
+///
+/// In order to provide a hierarchy, the mapper does not accept single elements, but rather
+/// iterators over elements. The more elements the iterator produces, the deeper down the hierarchy
+/// we go.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct HashMapper;
+
+impl HashMapper {
+ /// Create a new [`HashMapper`].
+ pub fn new() -> HashMapper {
+ HashMapper
+ }
+}
+
+impl<I, F> Mapper<I, Scalar> for HashMapper
+where
+ I: IntoIterator<Item = F>,
+ F: Hash,
+{
+ fn map_identity(&self, input: I) -> Result<Vec<Scalar>> {
+ Ok(input
+ .into_iter()
+ .map(|element| Sha3Hasher::hash(&element))
+ .collect())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_encrypt_decrypt() {
+ let mut rng = rand::thread_rng();
+ let kem = HybridKem::new(5);
+ let (public_key, master_key) = kem.setup(&mut rng).unwrap();
+
+ let identity = &[1, 2, 3] as &[_];
+ let secret_key = kem
+ .generate_key(&mut rng, &public_key, &master_key, identity)
+ .unwrap();
+ let message = b"Hello, world!";
+ let ciphertext = kem
+ .encrypt(&mut rng, &public_key, identity, message)
+ .unwrap();
+ let decryption = kem.decrypt(&public_key, &secret_key, &ciphertext).unwrap();
+ assert_eq!(message.as_slice(), decryption.as_slice());
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ce38f07
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,43 @@
+pub mod error;
+pub mod hibe;
+pub mod kem;
+
+use error::Result;
+
+/// A trait to provide byte-level access to objects.
+pub trait ByteAccess {
+ /// Provides access to the bytes.
+ ///
+ /// Unlike [`AsRef`], there are no statements made about the performance of this operation.
+ /// This operation will allocate a fresh vector, and the byte representation may or may not
+ /// have to be computed first.
+ fn bytes(&self) -> Vec<u8>;
+
+ /// Provide a short fingerprint of the bytes.
+ ///
+ /// This can be used to "summarize" long keys when displaying them, to still provide
+ /// distinguishing features but to not print out the whole key.
+ ///
+ /// By default, this method uses the first 16 bytes of the [`ByteAccess::bytes`]
+ /// representation, and formats them as a hex string.
+ fn fingerprint(&self) -> String {
+ hex::encode(&self.bytes()[..16])
+ }
+}
+
+/// A trait to mark objects that can map from an application-specific identity to a HIBE-specific
+/// identity.
+///
+/// A mapper can be implemented multiple times for a single struct, thereby providing multiple
+/// (equivalent) ways to map.
+pub trait Mapper<F, T> {
+ fn map_identity(&self, input: F) -> Result<Vec<T>>;
+}
+
+/// [`Mapper`] is automatically implemented for functions and closures that match the signature of
+/// [`Mapper::map_identity`].
+impl<X, Y, F: Fn(X) -> Result<Vec<Y>>> Mapper<X, Y> for F {
+ fn map_identity(&self, input: X) -> Result<Vec<Y>> {
+ self(input)
+ }
+}