// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright by contributors to this project. // SPDX-License-Identifier: (Apache-2.0 OR MIT) use mls_rs::{ client_builder::MlsConfig, error::MlsError, identity::{ basic::{BasicCredential, BasicIdentityProvider}, SigningIdentity, }, mls_rules::{CommitOptions, DefaultMlsRules}, CipherSuite, CipherSuiteProvider, Client, CryptoProvider, Group, }; const CIPHERSUITE: CipherSuite = CipherSuite::CURVE25519_AES128; const GROUP_SIZES: [usize; 8] = [2, 3, 5, 9, 17, 33, 65, 129]; enum Case { Best, Worst, } fn bench_commit_size( case_group: Case, crypto_provider: &P, ) -> Result<(Vec, Vec), MlsError> { let mut small_bench = vec![]; let mut large_bench = vec![]; for num_groups in GROUP_SIZES.iter().copied() { let (small_commit, large_commit) = match case_group { Case::Best => { let mut groups = make_groups_best_case(num_groups, crypto_provider)?; let small_commit = groups[num_groups - 1].commit(vec![])?.commit_message; let large_commit = groups[0].commit(vec![])?.commit_message; (small_commit, large_commit) } Case::Worst => { let mut groups = make_groups_worst_case(num_groups, crypto_provider)?; let small_commit = groups[num_groups - 1].commit(vec![])?.commit_message; let large_commit = groups[0].commit(vec![])?.commit_message; (small_commit, large_commit) } }; small_bench.push(small_commit.to_bytes()?.len()); large_bench.push(large_commit.to_bytes()?.len()); } Ok((small_bench, large_bench)) } // Bob[0] crates a group. Repeat for `i=0` to `num_groups - 1` times : Bob[i] adds Bob[i+1] fn make_groups_best_case( num_groups: usize, crypto_provider: &P, ) -> Result>, MlsError> { let bob_client = make_client(crypto_provider.clone(), &make_name(0))?; let bob_group = bob_client.create_group(Default::default())?; let mut groups = vec![bob_group]; for i in 0..(num_groups - 1) { let bob_client = make_client(crypto_provider.clone(), &make_name(i + 1))?; // The new client generates a key package. let bob_kpkg = bob_client.generate_key_package_message()?; // Last group sends a commit adding the new client to the group. let commit = groups .last_mut() .unwrap() .commit_builder() .add_member(bob_kpkg)? .build()?; // All other groups process the commit. for group in groups.iter_mut().rev().skip(1) { group.process_incoming_message(commit.commit_message.clone())?; } // The last group applies the generated commit. groups.last_mut().unwrap().apply_pending_commit()?; // The new member joins. let (bob_group, _info) = bob_client.join_group(None, &commit.welcome_messages[0])?; groups.push(bob_group); } Ok(groups) } // Alice creates a group by adding `num_groups - 1` clients in one commit. fn make_groups_worst_case( num_groups: usize, crypto_provider: &P, ) -> Result>, MlsError> { let alice_client = make_client(crypto_provider.clone(), &make_name(0))?; let mut alice_group = alice_client.create_group(Default::default())?; let bob_clients = (0..(num_groups - 1)) .map(|i| make_client(crypto_provider.clone(), &make_name(i + 1))) .collect::, _>>()?; // Alice adds all Bob's clients in a single commit. let mut commit_builder = alice_group.commit_builder(); for bob_client in &bob_clients { let bob_kpkg = bob_client.generate_key_package_message()?; commit_builder = commit_builder.add_member(bob_kpkg)?; } let welcome_message = &commit_builder.build()?.welcome_messages[0]; alice_group.apply_pending_commit()?; // Bob's clients join the group. let mut groups = vec![alice_group]; for bob_client in &bob_clients { let (bob_group, _info) = bob_client.join_group(None, welcome_message)?; groups.push(bob_group); } Ok(groups) } fn make_client( crypto_provider: P, name: &str, ) -> Result, MlsError> { let cipher_suite = crypto_provider.cipher_suite_provider(CIPHERSUITE).unwrap(); // Generate a signature key pair. let (secret, public) = cipher_suite.signature_key_generate().unwrap(); // Create a basic credential for the session. // NOTE: BasicCredential is for demonstration purposes and not recommended for production. // X.509 credentials are recommended. let basic_identity = BasicCredential::new(name.as_bytes().to_vec()); let signing_identity = SigningIdentity::new(basic_identity.into_credential(), public); Ok(Client::builder() .identity_provider(BasicIdentityProvider) .crypto_provider(crypto_provider) .mls_rules( DefaultMlsRules::new() .with_commit_options(CommitOptions::new().with_path_required(true)), ) .signing_identity(signing_identity, secret, CIPHERSUITE) .build()) } fn make_name(i: usize) -> String { format!("bob {i:08}") } fn main() -> Result<(), MlsError> { let crypto_provider = mls_rs_crypto_openssl::OpensslCryptoProvider::default(); println!("Demonstrate that performance depends on a) group evolution and b) a members position in the tree.\n"); let (small_bench_bc, large_bench_bc) = bench_commit_size(Case::Best, &crypto_provider)?; let (small_bench_wc, large_bench_wc) = bench_commit_size(Case::Worst, &crypto_provider)?; println!("\nBest case a), worst case b) : commit size is θ(log(n)) bytes."); println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes :\n{large_bench_bc:?}"); println!("\nWorst case a), worst case b) : commit size is θ(n) bytes."); println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes :\n{large_bench_wc:?}"); println!( "\nBest case b) : if n-1 is a power of 2, commit size is θ(1) bytes, independent of a)." ); println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes, best case a) :\n{small_bench_bc:?}"); println!("commit sizes, worst case a) :\n{small_bench_wc:?}"); Ok(()) }