use alloc::{vec, vec::Vec};
use mls_rs_core::{
crypto::{CipherSuite, CipherSuiteProvider, SignatureSecretKey},
error::IntoAnyError,
extension::{Extension, ExtensionList},
group::GroupContext,
identity::{MemberValidationContext, SigningIdentity},
protocol_version::ProtocolVersion,
time::MlsTime,
};
use crate::{
client_config::ClientConfig,
error::MlsError,
group::{
cipher_suite_provider, confirmation_tag::ConfirmationTag, key_schedule::KeySchedule,
state_repo::GroupStateRepository, transcript_hash::InterimTranscriptHash, GroupState,
LeafIndex, LeafNode,
},
tree_kem::{
leaf_node_validator::{LeafNodeValidator, ValidationContext},
TreeKemPublic,
},
Group,
};
pub struct GroupBuilder<C> {
pub(crate) group_id: Option<Vec<u8>>,
pub(crate) protocol_version: ProtocolVersion,
pub(crate) group_context_extensions: ExtensionList,
pub(crate) leaf_node_extensions: ExtensionList,
pub(crate) now_time: Option<MlsTime>,
#[cfg(feature = "custom_start_epoch")]
pub(crate) start_epoch: u64,
pub(crate) config: C,
pub(crate) cipher_suite: CipherSuite,
pub(crate) signing_identity: SigningIdentity,
pub(crate) signer: SignatureSecretKey,
}
impl<C> GroupBuilder<C> {
pub(crate) fn new(
config: C,
cipher_suite: CipherSuite,
signing_identity: SigningIdentity,
signer: SignatureSecretKey,
) -> Self {
Self {
group_id: None,
protocol_version: ProtocolVersion::MLS_10,
group_context_extensions: Default::default(),
leaf_node_extensions: Default::default(),
now_time: None,
#[cfg(feature = "custom_start_epoch")]
start_epoch: 0,
config,
cipher_suite,
signing_identity,
signer,
}
}
pub fn with_group_id(mut self, group_id: Vec<u8>) -> Self {
self.group_id = Some(group_id);
self
}
pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
self.protocol_version = protocol_version;
self
}
pub fn with_group_context_extensions(mut self, extensions: ExtensionList) -> Self {
self.group_context_extensions = extensions;
self
}
pub fn with_group_context_extension(mut self, extension: Extension) -> Self {
self.group_context_extensions.set(extension);
self
}
pub fn with_leaf_node_extensions(mut self, extensions: ExtensionList) -> Self {
self.leaf_node_extensions = extensions;
self
}
pub fn with_leaf_node_extension(mut self, extension: Extension) -> Self {
self.leaf_node_extensions.set(extension);
self
}
pub fn with_now_time(mut self, now_time: MlsTime) -> Self {
self.now_time = Some(now_time);
self
}
#[cfg(feature = "custom_start_epoch")]
pub fn with_start_epoch(mut self, start_epoch: u64) -> Self {
self.start_epoch = start_epoch;
self
}
}
impl<C: ClientConfig + Clone> GroupBuilder<C> {
#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
pub async fn build(self) -> Result<Group<C>, MlsError> {
let cipher_suite_provider =
cipher_suite_provider(self.config.crypto_provider(), self.cipher_suite)?;
let (leaf_node, leaf_node_secret) = LeafNode::generate(
&cipher_suite_provider,
self.config.leaf_properties(self.leaf_node_extensions),
self.signing_identity,
&self.signer,
self.config.lifetime(self.now_time),
)
.await?;
let (mut public_tree, private_tree) = TreeKemPublic::derive(
leaf_node,
leaf_node_secret,
&self.config.identity_provider(),
&self.group_context_extensions,
)
.await?;
let tree_hash = public_tree.tree_hash(&cipher_suite_provider).await?;
let group_id = self.group_id.map(Ok).unwrap_or_else(|| {
cipher_suite_provider
.random_bytes_vec(cipher_suite_provider.kdf_extract_size())
.map_err(|e| MlsError::CryptoProviderError(e.into_any_error()))
})?;
let context = GroupContext::new(
self.protocol_version,
self.cipher_suite,
group_id,
tree_hash,
self.group_context_extensions,
);
#[cfg(feature = "custom_start_epoch")]
let context = GroupContext {
epoch: self.start_epoch,
..context
};
let identity_provider = self.config.identity_provider();
let member_validation_context = MemberValidationContext::ForNewGroup {
current_context: &context,
};
let leaf_node_validator = LeafNodeValidator::new(
&cipher_suite_provider,
&identity_provider,
member_validation_context,
);
leaf_node_validator
.check_if_valid(
public_tree.get_leaf_node(LeafIndex::unchecked(0))?,
ValidationContext::Add(self.now_time),
)
.await?;
let state_repo = GroupStateRepository::new(
#[cfg(feature = "prior_epoch")]
context.group_id.clone(),
self.config.group_state_storage(),
self.config.key_package_repo(),
None,
)?;
let key_schedule_result = KeySchedule::from_random_epoch_secret(
&cipher_suite_provider,
#[cfg(any(feature = "secret_tree_access", feature = "private_message"))]
public_tree.total_leaf_count(),
)
.await?;
let confirmation_tag = ConfirmationTag::create(
&key_schedule_result.confirmation_key,
&vec![].into(),
&cipher_suite_provider,
)
.await?;
let interim_hash = InterimTranscriptHash::create(
&cipher_suite_provider,
&vec![].into(),
&confirmation_tag,
)
.await?;
Ok(Group {
config: self.config,
state: GroupState::new(context, public_tree, interim_hash, confirmation_tag),
private_tree,
key_schedule: key_schedule_result.key_schedule,
#[cfg(feature = "by_ref_proposal")]
pending_updates: Default::default(),
pending_commit: Default::default(),
epoch_secrets: key_schedule_result.epoch_secrets,
state_repo,
cipher_suite_provider,
#[cfg(feature = "psk")]
previous_psk: None,
signer: self.signer,
#[cfg(test)]
commit_modifiers: Default::default(),
})
}
}