1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #![allow(
16 missing_docs,
17 unused_results,
18 clippy::unwrap_used,
19 clippy::expect_used,
20 clippy::indexing_slicing,
21 clippy::panic
22 )]
23
24 use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
25 use crypto_provider::{ed25519, CryptoProvider, CryptoRng};
26 use crypto_provider_default::CryptoProviderImpl;
27 use ldt_np_adv::{V0IdentityToken, V0Salt, V0_IDENTITY_TOKEN_LEN};
28
29 use np_adv::credential::matched::EmptyMatchedCredential;
30 use np_adv::deserialization_arena;
31 use np_adv::extended::serialize::AdvertisementType;
32 use np_adv::extended::V1IdentityToken;
33 use np_adv::legacy::serialize::UnencryptedEncoder;
34 use np_adv::{
35 credential::{book::*, v0::*, v1::*, *},
36 deserialize_advertisement,
37 extended::{
38 data_elements::{GenericDataElement, TxPowerDataElement},
39 deserialize::VerificationMode,
40 serialize::{
41 AdvBuilder as ExtendedAdvBuilder, MicEncryptedSectionEncoder, SectionBuilder,
42 SectionEncoder, SignedEncryptedSectionEncoder, UnencryptedSectionEncoder,
43 },
44 },
45 legacy::{
46 data_elements::actions::{ActionBits, ActionsDataElement},
47 serialize::{AdvBuilder as LegacyAdvBuilder, LdtEncoder},
48 },
49 shared_data::TxPower,
50 };
51 use np_hkdf::{DerivedSectionKeys, NpKeySeedHkdf};
52 use rand::{Rng as _, SeedableRng as _};
53 use strum::IntoEnumIterator;
54
deser_adv_v1_encrypted(c: &mut Criterion)55 pub fn deser_adv_v1_encrypted(c: &mut Criterion) {
56 let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
57
58 for crypto_type in CryptoMaterialType::iter() {
59 for &identity_type in &[VerificationMode::Mic, VerificationMode::Signature] {
60 for &num_identities in &[10, 100, 1000] {
61 for &num_sections in &[1, 2] {
62 // measure worst-case performance -- the correct identities will be the last
63 // num_sections of the identities to be tried
64 c.bench_function(
65 &format!(
66 "Deser V1 encrypted: crypto={crypto_type:?}/mode={identity_type:?}/ids={num_identities}/sections={num_sections}"
67 ),
68 |b| {
69 let identities = (0..num_identities)
70 .map(|_| V1Identity::random::<CryptoProviderImpl>(&mut crypto_rng))
71 .collect::<Vec<_>>();
72
73 let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Encrypted);
74
75 // take the first n identities, one section per identity
76 for identity in identities.iter().take(num_sections) {
77 let broadcast_cm = V1BroadcastCredential::new(
78 identity.key_seed,
79 identity.identity_token,
80 identity.private_key.clone(),
81 );
82 match identity_type {
83 VerificationMode::Mic => {
84 let mut sb = adv_builder
85 .section_builder(MicEncryptedSectionEncoder::<_>::new_random_salt::<CryptoProviderImpl>(
86 &mut crypto_rng,
87 &broadcast_cm,
88 ))
89 .unwrap();
90
91 add_des(&mut sb);
92 sb.add_to_advertisement::<CryptoProviderImpl>();
93 }
94 VerificationMode::Signature => {
95 let mut sb = adv_builder
96 .section_builder(SignedEncryptedSectionEncoder::new_random_salt::<CryptoProviderImpl>(
97 &mut crypto_rng,
98 &broadcast_cm,
99 ))
100 .unwrap();
101
102 add_des(&mut sb);
103 sb.add_to_advertisement::<CryptoProviderImpl>();
104 }
105 }
106 }
107
108 let adv = adv_builder.into_advertisement();
109
110 run_with_v1_creds::<
111 CryptoProviderImpl
112 >(
113 b, crypto_type, identities, adv.as_slice(),
114 )
115 },
116 );
117 }
118 }
119 }
120 }
121 }
122
deser_adv_v1_plaintext(c: &mut Criterion)123 pub fn deser_adv_v1_plaintext(c: &mut Criterion) {
124 c.bench_function("Deser V1 plaintext: sections=1", |b| {
125 let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Plaintext);
126
127 let mut sb = adv_builder.section_builder(UnencryptedSectionEncoder).unwrap();
128
129 add_des(&mut sb);
130 sb.add_to_advertisement::<CryptoProviderImpl>();
131
132 let adv = adv_builder.into_advertisement();
133
134 run_with_v1_creds::<CryptoProviderImpl>(
135 b,
136 CryptoMaterialType::MinFootprint,
137 vec![],
138 adv.as_slice(),
139 )
140 });
141 }
142
deser_adv_v0_encrypted(c: &mut Criterion)143 pub fn deser_adv_v0_encrypted(c: &mut Criterion) {
144 let mut rng = rand::rngs::StdRng::from_entropy();
145 for crypto_type in CryptoMaterialType::iter() {
146 for &num_identities in &[10, 100, 1000] {
147 // measure worst-case performance -- the correct identities will be the last
148 // num_sections of the identities to be tried
149 c.bench_function(
150 &format!("Deser V0 encrypted: crypto={crypto_type:?}/ids={num_identities}"),
151 |b| {
152 let identities = (0..num_identities)
153 .map(|_| V0Identity::random(&mut rng))
154 .collect::<Vec<_>>();
155
156 let identity = &identities[0];
157
158 let broadcast_cm =
159 V0BroadcastCredential::new(identity.key_seed, identity.identity_token);
160
161 let mut adv_builder =
162 LegacyAdvBuilder::new(LdtEncoder::<CryptoProviderImpl>::new(
163 V0Salt::from(rng.gen::<[u8; 2]>()),
164 &broadcast_cm,
165 ));
166
167 let action_bits = ActionBits::default();
168 adv_builder.add_data_element(ActionsDataElement::from(action_bits)).unwrap();
169
170 let adv = adv_builder.into_advertisement().unwrap();
171
172 run_with_v0_creds::<CryptoProviderImpl>(
173 b,
174 crypto_type,
175 identities,
176 adv.as_slice(),
177 )
178 },
179 );
180 }
181 }
182 }
183
deser_adv_v0_plaintext(c: &mut Criterion)184 pub fn deser_adv_v0_plaintext(c: &mut Criterion) {
185 let mut adv_builder = LegacyAdvBuilder::new(UnencryptedEncoder);
186
187 let action_bits = ActionBits::default();
188 adv_builder.add_data_element(ActionsDataElement::from(action_bits)).unwrap();
189 let adv = adv_builder.into_advertisement().unwrap();
190
191 let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
192 0,
193 0,
194 CryptoProviderImpl,
195 >(&[], &[]);
196
197 for &num_advs in &[1, 10, 100, 1000] {
198 c.bench_function(
199 format!("Deser V0 plaintext with {num_advs} advertisements").as_str(),
200 |b| {
201 b.iter(|| {
202 for _ in 0..num_advs {
203 black_box(
204 deserialize_advertisement::<_, CryptoProviderImpl>(
205 deserialization_arena!(),
206 black_box(adv.as_slice()),
207 black_box(&cred_book),
208 )
209 .expect("Should succeed"),
210 );
211 }
212 })
213 },
214 );
215 }
216 }
217
218 /// Benchmark decrypting a V0 advertisement with credentials built from the reversed list of
219 /// identities
run_with_v0_creds<C>( b: &mut Bencher, crypto_material_type: CryptoMaterialType, identities: Vec<V0Identity>, adv: &[u8], ) where C: CryptoProvider,220 fn run_with_v0_creds<C>(
221 b: &mut Bencher,
222 crypto_material_type: CryptoMaterialType,
223 identities: Vec<V0Identity>,
224 adv: &[u8],
225 ) where
226 C: CryptoProvider,
227 {
228 let mut creds = identities
229 .into_iter()
230 .map(|identity| identity.into_discovery_credential::<C>())
231 .map(|crypto_material| MatchableCredential {
232 discovery_credential: crypto_material,
233 match_data: EmptyMatchedCredential,
234 })
235 .collect::<Vec<_>>();
236
237 // reverse the identities so that we're scanning to the end of the
238 // cred source for predictably bad performance
239 creds.reverse();
240
241 match crypto_material_type {
242 CryptoMaterialType::MinFootprint => {
243 // Cache size of 0 => only min-footprint creds
244 let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::<
245 0,
246 0,
247 CryptoProviderImpl,
248 >(&creds, &[]);
249
250 b.iter(|| {
251 black_box(
252 deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
253 .map(|_| 0_u8)
254 .unwrap(),
255 )
256 });
257 }
258 CryptoMaterialType::Precalculated => {
259 let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>(
260 creds,
261 core::iter::empty(),
262 );
263 b.iter(|| {
264 black_box(
265 deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
266 .map(|_| 0_u8)
267 .unwrap(),
268 )
269 });
270 }
271 }
272 }
273
274 /// Benchmark decrypting a V1 advertisement with credentials built from the reversed list of
275 /// identities
run_with_v1_creds<C>( b: &mut Bencher, crypto_material_type: CryptoMaterialType, identities: Vec<V1Identity>, adv: &[u8], ) where C: CryptoProvider,276 fn run_with_v1_creds<C>(
277 b: &mut Bencher,
278 crypto_material_type: CryptoMaterialType,
279 identities: Vec<V1Identity>,
280 adv: &[u8],
281 ) where
282 C: CryptoProvider,
283 {
284 let mut creds = identities
285 .into_iter()
286 .map(|identity| identity.into_discovery_credential::<C>())
287 .map(|crypto_material| MatchableCredential {
288 discovery_credential: crypto_material,
289 match_data: EmptyMatchedCredential,
290 })
291 .collect::<Vec<_>>();
292
293 // reverse the identities so that we're scanning to the end of the
294 // cred source for predictably bad performance
295 creds.reverse();
296
297 match crypto_material_type {
298 CryptoMaterialType::MinFootprint => {
299 // Cache size of 0 => only min-footprint creds
300 let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::<
301 0,
302 0,
303 CryptoProviderImpl,
304 >(&[], &creds);
305
306 b.iter(|| {
307 black_box(
308 deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
309 .map(|_| 0_u8)
310 .unwrap(),
311 )
312 });
313 }
314 CryptoMaterialType::Precalculated => {
315 let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>(
316 core::iter::empty(),
317 creds,
318 );
319 b.iter(|| {
320 black_box(
321 deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
322 .map(|_| 0_u8)
323 .unwrap(),
324 )
325 });
326 }
327 }
328 }
329
add_des<I: SectionEncoder>( sb: &mut SectionBuilder<&mut np_adv::extended::serialize::AdvBuilder, I>, )330 fn add_des<I: SectionEncoder>(
331 sb: &mut SectionBuilder<&mut np_adv::extended::serialize::AdvBuilder, I>,
332 ) {
333 sb.add_de_res(|_| TxPower::try_from(17).map(TxPowerDataElement::from)).unwrap();
334 sb.add_de_res(|_| GenericDataElement::try_from(100_u32.into(), &[0; 10])).unwrap();
335 }
336 criterion_group!(
337 benches,
338 deser_adv_v1_encrypted,
339 deser_adv_v1_plaintext,
340 deser_adv_v0_encrypted,
341 deser_adv_v0_plaintext
342 );
343 criterion_main!(benches);
344
345 struct V0Identity {
346 key_seed: [u8; 32],
347 identity_token: V0IdentityToken,
348 }
349
350 impl V0Identity {
351 /// Generate a new identity with random crypto material
random<R: rand::Rng + rand::CryptoRng>(rng: &mut R) -> Self352 fn random<R: rand::Rng + rand::CryptoRng>(rng: &mut R) -> Self {
353 Self {
354 key_seed: rng.gen(),
355 identity_token: V0IdentityToken::from(rng.gen::<[u8; V0_IDENTITY_TOKEN_LEN]>()),
356 }
357 }
358 /// Convert this `V0Identity` into a V0 discovery credential.
into_discovery_credential<C: CryptoProvider>(self) -> V0DiscoveryCredential359 fn into_discovery_credential<C: CryptoProvider>(self) -> V0DiscoveryCredential {
360 let hkdf = NpKeySeedHkdf::<C>::new(&self.key_seed);
361 V0DiscoveryCredential::new(
362 self.key_seed,
363 hkdf.v0_identity_token_hmac_key().calculate_hmac::<C>(self.identity_token.as_slice()),
364 )
365 }
366 }
367
368 struct V1Identity {
369 key_seed: [u8; 32],
370 identity_token: V1IdentityToken,
371 private_key: ed25519::PrivateKey,
372 }
373
374 impl V1Identity {
375 /// Generate a new identity with random crypto material
random<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self376 fn random<C: CryptoProvider>(rng: &mut C::CryptoRng) -> Self {
377 Self {
378 key_seed: rng.gen(),
379 identity_token: rng.gen(),
380 private_key: ed25519::PrivateKey::generate::<C::Ed25519>(),
381 }
382 }
383 /// Convert this `V1Identity` into a `V1DiscoveryCredential`.
into_discovery_credential<C: CryptoProvider>(self) -> V1DiscoveryCredential384 fn into_discovery_credential<C: CryptoProvider>(self) -> V1DiscoveryCredential {
385 let hkdf = NpKeySeedHkdf::<C>::new(&self.key_seed);
386
387 V1DiscoveryCredential::new(
388 self.key_seed,
389 hkdf.v1_mic_short_salt_keys()
390 .identity_token_hmac_key()
391 .calculate_hmac::<C>(self.identity_token.as_slice()),
392 hkdf.v1_mic_extended_salt_keys()
393 .identity_token_hmac_key()
394 .calculate_hmac::<C>(self.identity_token.as_slice()),
395 hkdf.v1_signature_keys()
396 .identity_token_hmac_key()
397 .calculate_hmac::<C>(self.identity_token.as_slice()),
398 self.private_key.derive_public_key::<C::Ed25519>(),
399 )
400 }
401 }
402
403 #[derive(strum_macros::EnumIter, Clone, Copy, Debug)]
404 enum CryptoMaterialType {
405 MinFootprint,
406 Precalculated,
407 }
408