olm_rs/
account.rs

1// Copyright 2020 Johannes Hayeß
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//! This module wraps around all functions following the pattern `olm_account_*`.
16
17use crate::errors::{self, OlmAccountError, OlmSessionError};
18use crate::getrandom;
19use crate::session::{OlmSession, PreKeyMessage};
20use crate::{ByteBuf, PicklingMode};
21
22#[cfg(feature = "deserialization")]
23use std::collections::{hash_map::Iter, hash_map::Keys, hash_map::Values, HashMap};
24use std::ffi::CStr;
25
26#[cfg(feature = "deserialization")]
27use serde::Deserialize;
28#[cfg(feature = "deserialization")]
29use serde_json::Value;
30use zeroize::Zeroizing;
31
32/// An olm account manages all cryptographic keys used on a device.
33/// ```
34/// use olm_rs::account::OlmAccount;
35///
36/// let olm_account = OlmAccount::new();
37/// println!("{:?}", olm_account.identity_keys());
38/// ```
39pub struct OlmAccount {
40    /// Pointer by which libolm acquires the data saved in an instance of OlmAccount
41    pub(crate) olm_account_ptr: *mut olm_sys::OlmAccount,
42    _olm_account_buf: ByteBuf,
43}
44
45#[cfg(feature = "deserialization")]
46/// Struct representing the parsed result of [`OlmAccount::identity_keys()`].
47#[derive(Deserialize, Debug, PartialEq)]
48pub struct IdentityKeys {
49    #[serde(flatten)]
50    keys: HashMap<String, String>,
51}
52
53#[cfg(feature = "deserialization")]
54impl IdentityKeys {
55    /// Get the public part of the ed25519 key of the account.
56    pub fn ed25519(&self) -> &str {
57        &self.keys["ed25519"]
58    }
59
60    /// Get the public part of the curve25519 key of the account.
61    pub fn curve25519(&self) -> &str {
62        &self.keys["curve25519"]
63    }
64
65    /// Get a reference to the key of the given key type.
66    pub fn get(&self, key_type: &str) -> Option<&str> {
67        let ret = self.keys.get(key_type);
68        ret.map(|x| &**x)
69    }
70
71    /// An iterator visiting all public keys of the account.
72    pub fn values(&self) -> Values<String, String> {
73        self.keys.values()
74    }
75
76    /// An iterator visiting all key types of the account.
77    pub fn keys(&self) -> Keys<String, String> {
78        self.keys.keys()
79    }
80
81    /// An iterator visiting all key-type, key pairs of the account.
82    pub fn iter(&self) -> Iter<String, String> {
83        self.keys.iter()
84    }
85
86    /// Returns true if the account contains a key with the given key type.
87    pub fn contains_key(&self, key_type: &str) -> bool {
88        self.keys.contains_key(key_type)
89    }
90}
91
92#[cfg(feature = "deserialization")]
93/// Struct representing the parsed result of [`OlmAccount::fallback_key()`].
94#[derive(Deserialize, Debug, PartialEq)]
95pub struct FallbackKey {
96    index: String,
97    key: String,
98}
99
100#[cfg(feature = "deserialization")]
101impl FallbackKey {
102    /// Get the public part of this fallback key.
103    pub fn curve25519(&self) -> &str {
104        &self.key
105    }
106
107    /// Get the index associated with this fallback key.
108    pub fn index(&self) -> &str {
109        &self.index
110    }
111}
112
113#[cfg(feature = "deserialization")]
114#[derive(Deserialize, Debug, PartialEq)]
115/// Struct representing the the one-time keys.
116/// The keys can be accessed in a map-like fashion.
117pub struct OneTimeKeys {
118    #[serde(flatten)]
119    keys: HashMap<String, HashMap<String, String>>,
120}
121
122#[cfg(feature = "deserialization")]
123impl OneTimeKeys {
124    /// Get the HashMap containing the curve25519 one-time keys.
125    /// This is the same as using `get("curve25519").unwrap()`
126    pub fn curve25519(&self) -> &HashMap<String, String> {
127        &self.keys["curve25519"]
128    }
129
130    /// Get a reference to the hashmap corresponding to given key type.
131    pub fn get(&self, key_type: &str) -> Option<&HashMap<String, String>> {
132        self.keys.get(key_type)
133    }
134
135    /// An iterator visiting all one-time key hashmaps in an arbitrary order.
136    pub fn values(&self) -> Values<String, HashMap<String, String>> {
137        self.keys.values()
138    }
139
140    /// An iterator visiting all one-time key types in an arbitrary order.
141    pub fn keys(&self) -> Keys<String, HashMap<String, String>> {
142        self.keys.keys()
143    }
144
145    /// An iterator visiting all one-time key types and their respective
146    /// key hashmaps in an arbitrary order.
147    pub fn iter(&self) -> Iter<String, HashMap<String, String>> {
148        self.keys.iter()
149    }
150
151    /// Returns `true` if the struct contains the given key type.
152    /// This does not mean that there are any keys for the given key type.
153    pub fn contains_key(&self, key_type: &str) -> bool {
154        self.keys.contains_key(key_type)
155    }
156}
157
158impl OlmAccount {
159    /// Creates a new instance of OlmAccount. During the instantiation the Ed25519 fingerprint key pair
160    /// and the Curve25519 identity key pair are generated. For more information see
161    /// [here](https://matrix.org/docs/guides/e2e_implementation.html#keys-used-in-end-to-end-encryption).
162    ///
163    /// # C-API equivalent
164    /// `olm_create_account`
165    ///
166    /// # Panics
167    /// * `NOT_ENOUGH_RANDOM` for OlmAccount's creation
168    ///
169    pub fn new() -> Self {
170        // allocate buffer for OlmAccount to be written into
171        let mut olm_account_buf = ByteBuf::new(unsafe { olm_sys::olm_account_size() });
172
173        // let libolm populate the allocated memory
174        let olm_account_ptr = unsafe { olm_sys::olm_account(olm_account_buf.as_mut_void_ptr()) };
175
176        let create_error = {
177            // determine optimal length of the random buffer
178            let random_len = unsafe { olm_sys::olm_create_account_random_length(olm_account_ptr) };
179            let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
180            getrandom(&mut random_buf);
181
182            // create OlmAccount with supplied random data
183            unsafe {
184                olm_sys::olm_create_account(
185                    olm_account_ptr,
186                    random_buf.as_mut_ptr() as *mut _,
187                    random_len,
188                )
189            }
190        };
191
192        if create_error == errors::olm_error() {
193            errors::handle_fatal_error(Self::last_error(olm_account_ptr));
194        }
195        OlmAccount {
196            olm_account_ptr,
197            _olm_account_buf: olm_account_buf,
198        }
199    }
200
201    /// Serialises an [`OlmAccount`] to encrypted Base64.
202    ///
203    /// # C-API equivalent
204    /// `olm_pickle_account`
205    ///
206    /// # Example
207    /// ```
208    /// use olm_rs::account::OlmAccount;
209    /// use olm_rs::PicklingMode;
210    ///
211    /// let identity_keys;
212    /// let olm_account = OlmAccount::new();
213    /// identity_keys = olm_account.identity_keys();
214    /// let pickled = olm_account.pickle(PicklingMode::Unencrypted);
215    /// let olm_account_2 = OlmAccount::unpickle(pickled, PicklingMode::Unencrypted).unwrap();
216    /// let identity_keys_2 = olm_account_2.identity_keys();
217    ///
218    /// assert_eq!(identity_keys, identity_keys_2);
219    /// ```
220    ///
221    /// # Panics
222    /// * `OUTPUT_BUFFER_TOO_SMALL` for OlmAccount's pickled buffer
223    /// * on malformed UTF-8 coding for pickling provided by libolm
224    ///
225    pub fn pickle(&self, mode: PicklingMode) -> String {
226        let mut pickled_buf: Vec<u8> =
227            vec![0; unsafe { olm_sys::olm_pickle_account_length(self.olm_account_ptr) }];
228
229        let pickle_error = {
230            let key = Zeroizing::new(crate::convert_pickling_mode_to_key(mode));
231
232            unsafe {
233                olm_sys::olm_pickle_account(
234                    self.olm_account_ptr,
235                    key.as_ptr() as *const _,
236                    key.len(),
237                    pickled_buf.as_mut_ptr() as *mut _,
238                    pickled_buf.len(),
239                )
240            }
241        };
242
243        let pickled_result = String::from_utf8(pickled_buf).unwrap();
244
245        if pickle_error == errors::olm_error() {
246            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
247        }
248
249        pickled_result
250    }
251
252    /// Deserialises from encrypted Base64 that was previously obtained by pickling an [`OlmAccount`].
253    ///
254    /// # C-API equivalent
255    /// `olm_unpickle_account`
256    ///
257    /// # Errors
258    /// * `BadAccountKey` if the key doesn't match the one the account was encrypted with
259    /// * `InvalidBase64` if decoding the supplied `pickled` string slice fails
260    ///
261    pub fn unpickle(mut pickled: String, mode: PicklingMode) -> Result<Self, OlmAccountError> {
262        let pickled_len = pickled.len();
263        let pickled_buf = Box::new(unsafe { pickled.as_bytes_mut() });
264
265        let mut olm_account_buf = ByteBuf::new(unsafe { olm_sys::olm_account_size() });
266        let olm_account_ptr = unsafe { olm_sys::olm_account(olm_account_buf.as_mut_void_ptr()) };
267
268        let unpickle_error = {
269            let key = Zeroizing::new(crate::convert_pickling_mode_to_key(mode));
270
271            unsafe {
272                olm_sys::olm_unpickle_account(
273                    olm_account_ptr,
274                    key.as_ptr() as *const _,
275                    key.len(),
276                    pickled_buf.as_mut_ptr() as *mut _,
277                    pickled_len,
278                )
279            }
280        };
281
282        if unpickle_error == errors::olm_error() {
283            Err(Self::last_error(olm_account_ptr))
284        } else {
285            Ok(OlmAccount {
286                olm_account_ptr,
287                _olm_account_buf: olm_account_buf,
288            })
289        }
290    }
291
292    /// Returns the account's public identity keys already formatted as JSON and BASE64.
293    ///
294    /// # C-API equivalent
295    /// `olm_account_identity_keys`
296    ///
297    /// # Panics
298    /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied identity keys buffer
299    /// * on malformed UTF-8 coding of the identity keys provided by libolm
300    ///
301    pub fn identity_keys(&self) -> String {
302        // get buffer length of identity keys
303        let keys_len = unsafe { olm_sys::olm_account_identity_keys_length(self.olm_account_ptr) };
304        let mut identity_keys_buf: Vec<u8> = vec![0; keys_len];
305
306        // write keys data in the keys buffer
307        let identity_keys_error = unsafe {
308            olm_sys::olm_account_identity_keys(
309                self.olm_account_ptr,
310                identity_keys_buf.as_mut_ptr() as *mut _,
311                keys_len,
312            )
313        };
314
315        // String is constructed from the keys buffer and memory is freed after exiting the scope.
316        // No memory should be leaked.
317        let identity_keys_result = String::from_utf8(identity_keys_buf).unwrap();
318
319        if identity_keys_error == errors::olm_error() {
320            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
321        }
322
323        identity_keys_result
324    }
325
326    /// Returns the account's public identity keys.
327    #[cfg(feature = "deserialization")]
328    pub fn parsed_identity_keys(&self) -> IdentityKeys {
329        serde_json::from_str(&self.identity_keys()).expect("Can't deserialize identity keys")
330    }
331
332    /// Returns the last error that occurred for an OlmAccount.
333    /// Since error codes are encoded as CStrings by libolm,
334    /// OlmAccountError::Unknown is returned on an unknown error code.
335    fn last_error(olm_account_ptr: *mut olm_sys::OlmAccount) -> OlmAccountError {
336        let error;
337        // get CString error code and convert to String
338        unsafe {
339            let error_raw = olm_sys::olm_account_last_error(olm_account_ptr);
340            error = CStr::from_ptr(error_raw).to_str().unwrap();
341        }
342
343        match error {
344            "BAD_ACCOUNT_KEY" => OlmAccountError::BadAccountKey,
345            "BAD_MESSAGE_KEY_ID" => OlmAccountError::BadMessageKeyId,
346            "INVALID_BASE64" => OlmAccountError::InvalidBase64,
347            "NOT_ENOUGH_RANDOM" => OlmAccountError::NotEnoughRandom,
348            "OUTPUT_BUFFER_TOO_SMALL" => OlmAccountError::OutputBufferTooSmall,
349            _ => OlmAccountError::Unknown,
350        }
351    }
352
353    /// Returns the signature of the supplied byte slice.
354    ///
355    /// # C-API equivalent
356    /// `olm_account_sign`
357    ///
358    /// # Panics
359    /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied signature buffer
360    /// * on malformed UTF-8 coding of the signature provided by libolm
361    ///
362    pub fn sign(&self, message: &str) -> String {
363        let message_buf = message.as_bytes();
364        let message_ptr = message_buf.as_ptr() as *const _;
365
366        let signature_len = unsafe { olm_sys::olm_account_signature_length(self.olm_account_ptr) };
367        let mut signature_buf: Vec<u8> = vec![0; signature_len];
368
369        let signature_error = unsafe {
370            olm_sys::olm_account_sign(
371                self.olm_account_ptr,
372                message_ptr,
373                message_buf.len(),
374                signature_buf.as_mut_ptr() as *mut _,
375                signature_len,
376            )
377        };
378
379        let signature_result = String::from_utf8(signature_buf).unwrap();
380
381        if signature_error == errors::olm_error() {
382            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
383        }
384
385        signature_result
386    }
387
388    /// Maximum number of one time keys that this OlmAccount can currently hold.
389    ///
390    /// # C-API equivalent
391    /// `olm_account_max_number_of_one_time_keys`
392    ///
393    pub fn max_number_of_one_time_keys(&self) -> usize {
394        unsafe { olm_sys::olm_account_max_number_of_one_time_keys(self.olm_account_ptr) }
395    }
396
397    /// Generates the supplied number of one time keys.
398    ///
399    /// # C-API equivalent
400    /// `olm_account_generate_one_time_keys`
401    ///
402    /// # Panics
403    /// * `NOT_ENOUGH_RANDOM` for the creation of one time keys
404    ///
405    pub fn generate_one_time_keys(&self, number_of_keys: usize) {
406        // Get correct length for the random buffer
407        let random_len = unsafe {
408            olm_sys::olm_account_generate_one_time_keys_random_length(
409                self.olm_account_ptr,
410                number_of_keys,
411            )
412        };
413
414        let generate_error = {
415            // Construct and populate random buffer
416            let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
417            getrandom(&mut random_buf);
418
419            // Call function for generating one time keys
420            unsafe {
421                olm_sys::olm_account_generate_one_time_keys(
422                    self.olm_account_ptr,
423                    number_of_keys,
424                    random_buf.as_mut_ptr() as *mut _,
425                    random_len,
426                )
427            }
428        };
429
430        if generate_error == errors::olm_error() {
431            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
432        }
433    }
434
435    /// Gets the OlmAccount's one time keys formatted as JSON.
436    ///
437    /// # C-API equivalent
438    /// `olm_account_one_time_keys`
439    ///
440    /// # Panics
441    /// * `OUTPUT_BUFFER_TOO_SMALL` for supplied one time keys buffer
442    /// * on malformed UTF-8 coding of the keys provided by libolm
443    ///
444    pub fn one_time_keys(&self) -> String {
445        // get buffer length of OTKs
446        let otks_len = unsafe { olm_sys::olm_account_one_time_keys_length(self.olm_account_ptr) };
447        let mut otks_buf: Vec<u8> = vec![0; otks_len];
448
449        // write OTKs data in the OTKs buffer
450        let otks_error = unsafe {
451            olm_sys::olm_account_one_time_keys(
452                self.olm_account_ptr,
453                otks_buf.as_mut_ptr() as *mut _,
454                otks_len,
455            )
456        };
457
458        // String is constructed from the OTKs buffer and memory is freed after exiting the scope.
459        let otks_result = String::from_utf8(otks_buf).unwrap();
460
461        if otks_error == errors::olm_error() {
462            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
463        }
464
465        otks_result
466    }
467
468    #[cfg(feature = "deserialization")]
469    /// Returns the account's one-time keys.
470    pub fn parsed_one_time_keys(&self) -> OneTimeKeys {
471        serde_json::from_str(&self.one_time_keys()).expect("Can't deserialize one-time keys.")
472    }
473
474    /// Mark the current set of one time keys as published.
475    ///
476    /// # C-API equivalent
477    /// `olm_account_mark_keys_as_published`
478    ///
479    pub fn mark_keys_as_published(&self) {
480        unsafe {
481            olm_sys::olm_account_mark_keys_as_published(self.olm_account_ptr);
482        }
483    }
484
485    /// Remove the one time key used to create the supplied session.
486    ///
487    /// # C-API equivalent
488    /// `olm_remove_one_time_keys`
489    ///
490    /// # Errors
491    /// * `BAD_MESSAGE_KEY_ID` when the account doesn't hold a matching one time key
492    ///
493    pub fn remove_one_time_keys(&self, session: &OlmSession) -> Result<(), OlmAccountError> {
494        let remove_error = unsafe {
495            olm_sys::olm_remove_one_time_keys(self.olm_account_ptr, session.olm_session_ptr)
496        };
497
498        if remove_error == errors::olm_error() {
499            Err(Self::last_error(self.olm_account_ptr))
500        } else {
501            Ok(())
502        }
503    }
504
505    /// Generates a new fallback key. Only one previous fallback key is stored.
506    ///
507    /// # C-API equivalent
508    /// `olm_account_generate_fallback_key`
509    ///
510    /// # Panics
511    /// * `NOT_ENOUGH_RANDOM`
512    ///
513    pub fn generate_fallback_key(&self) {
514        // determine optimal length of the random buffer
515        let random_len = unsafe {
516            olm_sys::olm_account_generate_fallback_key_random_length(self.olm_account_ptr)
517        };
518        let mut random_buf: Zeroizing<Vec<u8>> = Zeroizing::new(vec![0; random_len]);
519        getrandom(&mut random_buf);
520
521        // write keys data in the keys buffer
522        let fallback_key_error = unsafe {
523            olm_sys::olm_account_generate_fallback_key(
524                self.olm_account_ptr,
525                random_buf.as_mut_ptr() as *mut _,
526                random_len,
527            )
528        };
529
530        if fallback_key_error == errors::olm_error() {
531            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
532        }
533    }
534
535    /// Output fallback key of this account in JSON format.
536    ///
537    /// This is what the output looks like before generating an
538    /// initial fallback key:
539    /// ```json
540    /// {
541    ///     "curve25519": {}
542    /// }
543    /// ```
544    ///
545    /// And after:
546    /// ```json
547    /// {
548    ///     "curve25519": {
549    ///         "AAAAAQ": "u4XQpRre6j7peD4clRq9d56kRbwnVEAsavIiZHHZekY"
550    ///     }
551    /// }
552    /// ```
553    ///
554    /// # C-API equivalent
555    /// `olm_account_one_time_key`
556    ///
557    /// # Panics
558    /// * `OUTPUT_BUFFER_TOO_SMALL`
559    ///
560    pub fn fallback_key(&self) -> String {
561        // get buffer length of fallback keys
562        let fallback_len =
563            unsafe { olm_sys::olm_account_unpublished_fallback_key_length(self.olm_account_ptr) };
564        let mut fallback_buf: Vec<u8> = vec![0; fallback_len];
565
566        // write fallbacks key data in the fallback key buffer
567        let fallback_error = unsafe {
568            olm_sys::olm_account_unpublished_fallback_key(
569                self.olm_account_ptr,
570                fallback_buf.as_mut_ptr() as *mut _,
571                fallback_len,
572            )
573        };
574
575        if fallback_error == errors::olm_error() {
576            errors::handle_fatal_error(Self::last_error(self.olm_account_ptr));
577        }
578
579        // String is constructed from the fallback key buffer and memory is freed after exiting the scope.
580        String::from_utf8(fallback_buf).unwrap()
581    }
582
583    #[cfg(feature = "deserialization")]
584    /// Returns the account's fallback key. `None` if no fallback key has been generated yet.
585    pub fn parsed_fallback_key(&self) -> Option<FallbackKey> {
586        let parsed_fallback: Value =
587            serde_json::from_str(&self.fallback_key()).expect("Fallback key isn't in JSON format.");
588
589        // We make some assumptions about the structure of the parsed JSON.
590        // 1) At the top level is the index "curve25519" that contains an object
591        // 2) This object is either empty or contains a singular entry to a (key) string
592        parsed_fallback["curve25519"]
593            .as_object()
594            .unwrap()
595            .iter()
596            .next()
597            .map(|(index, key_value)| FallbackKey {
598                index: index.clone(),
599                key: key_value.as_str().unwrap().to_string(),
600            })
601    }
602
603    /// Creates an inbound session for sending/receiving messages from a received 'prekey' message.
604    ///
605    /// # Arguments
606    ///
607    /// * `message` - An Olm pre-key message that was encrypted for this
608    /// account.
609    ///
610    /// # Errors
611    /// * `InvalidBase64`
612    /// * `BadMessageVersion`
613    /// * `BadMessageFormat`
614    /// * `BadMessageKeyId`
615    ///
616    pub fn create_inbound_session(
617        &self,
618        message: PreKeyMessage,
619    ) -> Result<OlmSession, OlmSessionError> {
620        OlmSession::create_inbound_session(self, message)
621    }
622
623    /// Creates an inbound session for sending/receiving messages from a received 'prekey' message.
624    ///
625    /// * `their_identity_key` - The identity key of an Olm account that
626    /// encrypted this Olm message.
627    ///
628    /// * `message` - An Olm pre-key message that was encrypted for this
629    /// account.
630    ///
631    /// # Errors
632    /// * `InvalidBase64`
633    /// * `BadMessageVersion`
634    /// * `BadMessageFormat`
635    /// * `BadMessageKeyId`
636    ///
637    pub fn create_inbound_session_from(
638        &self,
639        their_identity_key: &str,
640        message: PreKeyMessage,
641    ) -> Result<OlmSession, OlmSessionError> {
642        OlmSession::create_inbound_session_from(self, their_identity_key, message)
643    }
644
645    /// Creates an outbound session for sending messages to a specific
646    /// identity and one time key.
647    ///
648    /// # Errors
649    /// * `InvalidBase64` for invalid base64 coding on supplied arguments
650    ///
651    /// # Panics
652    /// * `NotEnoughRandom` if not enough random data was supplied
653    ///
654    pub fn create_outbound_session(
655        &self,
656        their_identity_key: &str,
657        their_one_time_key: &str,
658    ) -> Result<OlmSession, OlmSessionError> {
659        OlmSession::create_outbound_session(self, their_identity_key, their_one_time_key)
660    }
661}
662
663impl Default for OlmAccount {
664    fn default() -> Self {
665        Self::new()
666    }
667}
668
669impl Drop for OlmAccount {
670    fn drop(&mut self) {
671        unsafe {
672            olm_sys::olm_clear_account(self.olm_account_ptr);
673        }
674    }
675}
676
677#[cfg(test)]
678mod test {
679    use super::OlmAccount;
680    use serde_json::Value;
681
682    #[test]
683    fn fallback_key() {
684        let account = OlmAccount::new();
685
686        assert!(account.parsed_fallback_key().is_none());
687
688        account.generate_fallback_key();
689
690        let parsed_fallback = account.parsed_fallback_key().unwrap();
691        let manually_parsed_fallback: Value = serde_json::from_str(&account.fallback_key())
692            .expect("Fallback key isn't in JSON format.");
693        assert_eq!(
694            parsed_fallback.curve25519(),
695            manually_parsed_fallback["curve25519"][parsed_fallback.index()]
696        );
697    }
698
699    #[cfg(feature = "deserialization")]
700    #[test]
701    fn parsed_keys() {
702        let account = OlmAccount::new();
703        let identity_keys = json::parse(&account.identity_keys()).unwrap();
704        let identity_keys_parsed = account.parsed_identity_keys();
705        assert_eq!(
706            identity_keys_parsed.curve25519(),
707            identity_keys["curve25519"]
708        );
709        assert_eq!(identity_keys_parsed.ed25519(), identity_keys["ed25519"]);
710    }
711}