1 // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 // Copyright by contributors to this project.
3 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
4 
5 use mls_rs::{
6     client_builder::MlsConfig,
7     error::MlsError,
8     identity::{
9         basic::{BasicCredential, BasicIdentityProvider},
10         SigningIdentity,
11     },
12     mls_rules::{CommitOptions, DefaultMlsRules},
13     CipherSuite, CipherSuiteProvider, Client, CryptoProvider, Group,
14 };
15 
16 const CIPHERSUITE: CipherSuite = CipherSuite::CURVE25519_AES128;
17 const GROUP_SIZES: [usize; 8] = [2, 3, 5, 9, 17, 33, 65, 129];
18 
19 enum Case {
20     Best,
21     Worst,
22 }
23 
bench_commit_size<P: CryptoProvider + Clone>( case_group: Case, crypto_provider: &P, ) -> Result<(Vec<usize>, Vec<usize>), MlsError>24 fn bench_commit_size<P: CryptoProvider + Clone>(
25     case_group: Case,
26     crypto_provider: &P,
27 ) -> Result<(Vec<usize>, Vec<usize>), MlsError> {
28     let mut small_bench = vec![];
29     let mut large_bench = vec![];
30 
31     for num_groups in GROUP_SIZES.iter().copied() {
32         let (small_commit, large_commit) = match case_group {
33             Case::Best => {
34                 let mut groups = make_groups_best_case(num_groups, crypto_provider)?;
35                 let small_commit = groups[num_groups - 1].commit(vec![])?.commit_message;
36                 let large_commit = groups[0].commit(vec![])?.commit_message;
37                 (small_commit, large_commit)
38             }
39             Case::Worst => {
40                 let mut groups = make_groups_worst_case(num_groups, crypto_provider)?;
41                 let small_commit = groups[num_groups - 1].commit(vec![])?.commit_message;
42                 let large_commit = groups[0].commit(vec![])?.commit_message;
43                 (small_commit, large_commit)
44             }
45         };
46 
47         small_bench.push(small_commit.to_bytes()?.len());
48         large_bench.push(large_commit.to_bytes()?.len());
49     }
50 
51     Ok((small_bench, large_bench))
52 }
53 
54 // Bob[0] crates a group. Repeat for `i=0` to `num_groups - 1` times : Bob[i] adds Bob[i+1]
make_groups_best_case<P: CryptoProvider + Clone>( num_groups: usize, crypto_provider: &P, ) -> Result<Vec<Group<impl MlsConfig>>, MlsError>55 fn make_groups_best_case<P: CryptoProvider + Clone>(
56     num_groups: usize,
57     crypto_provider: &P,
58 ) -> Result<Vec<Group<impl MlsConfig>>, MlsError> {
59     let bob_client = make_client(crypto_provider.clone(), &make_name(0))?;
60 
61     let bob_group = bob_client.create_group(Default::default())?;
62 
63     let mut groups = vec![bob_group];
64 
65     for i in 0..(num_groups - 1) {
66         let bob_client = make_client(crypto_provider.clone(), &make_name(i + 1))?;
67 
68         // The new client generates a key package.
69         let bob_kpkg = bob_client.generate_key_package_message()?;
70 
71         // Last group sends a commit adding the new client to the group.
72         let commit = groups
73             .last_mut()
74             .unwrap()
75             .commit_builder()
76             .add_member(bob_kpkg)?
77             .build()?;
78 
79         // All other groups process the commit.
80         for group in groups.iter_mut().rev().skip(1) {
81             group.process_incoming_message(commit.commit_message.clone())?;
82         }
83 
84         // The last group applies the generated commit.
85         groups.last_mut().unwrap().apply_pending_commit()?;
86 
87         // The new member joins.
88         let (bob_group, _info) = bob_client.join_group(None, &commit.welcome_messages[0])?;
89 
90         groups.push(bob_group);
91     }
92 
93     Ok(groups)
94 }
95 
96 // Alice creates a group by adding `num_groups - 1` clients in one commit.
make_groups_worst_case<P: CryptoProvider + Clone>( num_groups: usize, crypto_provider: &P, ) -> Result<Vec<Group<impl MlsConfig>>, MlsError>97 fn make_groups_worst_case<P: CryptoProvider + Clone>(
98     num_groups: usize,
99     crypto_provider: &P,
100 ) -> Result<Vec<Group<impl MlsConfig>>, MlsError> {
101     let alice_client = make_client(crypto_provider.clone(), &make_name(0))?;
102 
103     let mut alice_group = alice_client.create_group(Default::default())?;
104 
105     let bob_clients = (0..(num_groups - 1))
106         .map(|i| make_client(crypto_provider.clone(), &make_name(i + 1)))
107         .collect::<Result<Vec<_>, _>>()?;
108 
109     // Alice adds all Bob's clients in a single commit.
110     let mut commit_builder = alice_group.commit_builder();
111 
112     for bob_client in &bob_clients {
113         let bob_kpkg = bob_client.generate_key_package_message()?;
114         commit_builder = commit_builder.add_member(bob_kpkg)?;
115     }
116 
117     let welcome_message = &commit_builder.build()?.welcome_messages[0];
118 
119     alice_group.apply_pending_commit()?;
120 
121     // Bob's clients join the group.
122     let mut groups = vec![alice_group];
123 
124     for bob_client in &bob_clients {
125         let (bob_group, _info) = bob_client.join_group(None, welcome_message)?;
126         groups.push(bob_group);
127     }
128 
129     Ok(groups)
130 }
131 
make_client<P: CryptoProvider + Clone>( crypto_provider: P, name: &str, ) -> Result<Client<impl MlsConfig>, MlsError>132 fn make_client<P: CryptoProvider + Clone>(
133     crypto_provider: P,
134     name: &str,
135 ) -> Result<Client<impl MlsConfig>, MlsError> {
136     let cipher_suite = crypto_provider.cipher_suite_provider(CIPHERSUITE).unwrap();
137 
138     // Generate a signature key pair.
139     let (secret, public) = cipher_suite.signature_key_generate().unwrap();
140 
141     // Create a basic credential for the session.
142     // NOTE: BasicCredential is for demonstration purposes and not recommended for production.
143     // X.509 credentials are recommended.
144     let basic_identity = BasicCredential::new(name.as_bytes().to_vec());
145     let signing_identity = SigningIdentity::new(basic_identity.into_credential(), public);
146 
147     Ok(Client::builder()
148         .identity_provider(BasicIdentityProvider)
149         .crypto_provider(crypto_provider)
150         .mls_rules(
151             DefaultMlsRules::new()
152                 .with_commit_options(CommitOptions::new().with_path_required(true)),
153         )
154         .signing_identity(signing_identity, secret, CIPHERSUITE)
155         .build())
156 }
157 
make_name(i: usize) -> String158 fn make_name(i: usize) -> String {
159     format!("bob {i:08}")
160 }
161 
main() -> Result<(), MlsError>162 fn main() -> Result<(), MlsError> {
163     let crypto_provider = mls_rs_crypto_openssl::OpensslCryptoProvider::default();
164 
165     println!("Demonstrate that performance depends on a) group evolution and b) a members position in the tree.\n");
166 
167     let (small_bench_bc, large_bench_bc) = bench_commit_size(Case::Best, &crypto_provider)?;
168     let (small_bench_wc, large_bench_wc) = bench_commit_size(Case::Worst, &crypto_provider)?;
169 
170     println!("\nBest case a), worst case b) : commit size is θ(log(n)) bytes.");
171     println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes :\n{large_bench_bc:?}");
172 
173     println!("\nWorst case a), worst case b) : commit size is θ(n) bytes.");
174     println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes :\n{large_bench_wc:?}");
175 
176     println!(
177         "\nBest case b) : if n-1 is a power of 2, commit size is θ(1) bytes, independent of a)."
178     );
179     println!("group sizes n :\n{GROUP_SIZES:?}\ncommit sizes, best case a) :\n{small_bench_bc:?}");
180     println!("commit sizes, worst case a) :\n{small_bench_wc:?}");
181 
182     Ok(())
183 }
184