diff --git a/libraries/cbor/src/writer.rs b/libraries/cbor/src/writer.rs
index 127316091d3bfa9ebb7f3559cd977c3cbee117cb..0764851d37907a320251dd8767e2d505931d7f2a 100644
--- a/libraries/cbor/src/writer.rs
+++ b/libraries/cbor/src/writer.rs
@@ -36,7 +36,7 @@ impl<'a> Writer<'a> {
             return false;
         }
         match value {
-            Value::KeyValue(KeyType::Unsigned(unsigned)) => self.start_item(0, unsigned as u64),
+            Value::KeyValue(KeyType::Unsigned(unsigned)) => self.start_item(0, unsigned),
             Value::KeyValue(KeyType::Negative(negative)) => {
                 self.start_item(1, -(negative + 1) as u64)
             }
diff --git a/reproducible/reference_binaries_macos-10.15.sha256sum b/reproducible/reference_binaries_macos-10.15.sha256sum
index 9d3264a0e7dc57b8a5049bc54b3fa5cf0ca16064..b4211e92de9f2cd5b3b51123e29e09d98a23e794 100644
--- a/reproducible/reference_binaries_macos-10.15.sha256sum
+++ b/reproducible/reference_binaries_macos-10.15.sha256sum
@@ -1,9 +1,9 @@
 b113945b033eb229e3821542f5889769e5fd2e2ae3cb85c6d13a4e05a44a9866	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
-d81264ffbce075a78067b666db1988cd0e44e25c0e3f708ada28e90ff29f7339	target/nrf52840dk_merged.hex
+11a43fdafe73f59a5c715d1c850b28b1e0572cedd6acc93c860a3a36f6a3d4ac	target/nrf52840dk_merged.hex
 346016903ddf244a239162b7c703aafe7ec70a115175e2204892e874f930f6be	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
-0c50decccd94e93612f3342ab833a73959d96a754aa6845b755a779a2240c484	target/nrf52840_dongle_merged.hex
+6a9ecb26886e266e3f92bc57ba8d0f6441ecfbfffdfe60ab981113b92524d3a3	target/nrf52840_dongle_merged.hex
 adcc4caaea86f7b0d54111d3080802e7389a4e69a8f17945d026ee732ea8daa4	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
-bbcbd0e72e40257cea94a2fee829d6b2c365e8473f2fb8b6ae685cda4d925f7d	target/nrf52840_dongle_dfu_merged.hex
+55baecf731cf87966e1674fe6198aba2b955d33228192fb5b958e09b828aa3ea	target/nrf52840_dongle_dfu_merged.hex
 97a7dbdb7c3caa345307d5ff7f7607dad5c2cdc523b43c68d3b741ddce318e92	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
-d42f1ec737bd945ae2ddc74a5c179573b36fa720f9a7149b2af67a0e3667fac7	target/nrf52840_mdk_dfu_merged.hex
-166523347144bcf9b936f5c265dfb2d7de9ab28569881276df344a6a42423f1b	target/tab/ctap2.tab
+407b4bbc970901ce788ab3759571cf7e671f54a8c6a8546b7aff21ef96411df8	target/nrf52840_mdk_dfu_merged.hex
+5947ae9da7436dc41fd9e89cb1faacb197282d03d0e755e2ac0099dc87484b4a	target/tab/ctap2.tab
diff --git a/reproducible/reference_binaries_ubuntu-18.04.sha256sum b/reproducible/reference_binaries_ubuntu-18.04.sha256sum
index 3aca0952d9c38f62e4edcae3d4f41a0cdb9f0d5d..5e45b7f231bd63951588e0cc484d6f01703cbfb4 100644
--- a/reproducible/reference_binaries_ubuntu-18.04.sha256sum
+++ b/reproducible/reference_binaries_ubuntu-18.04.sha256sum
@@ -1,9 +1,9 @@
 921d6fc31f7235456dd41abc7e634a37ee87b5016b80c979d20ac5d3fcfc6b6b	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
-776dab253e0903bffc54a9aeb09323717a4d69e6f1db8a4d88641378a89e47fe	target/nrf52840dk_merged.hex
+223072356135c3d1f2114c34a29dd498da4a958722167b425c844333b48f82ef	target/nrf52840dk_merged.hex
 aab5bdc406b1e874b83872c9358d310070b3ce948ec0e20c054fb923ec879249	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
-945bfb77e7852d5d6425436208517c478af4ef66a675eded14f768de81512ab4	target/nrf52840_dongle_merged.hex
+98afad474b45a9ec8bddfcac6e61b149a25d5ed241e98bf1fa1174fa2efc91d2	target/nrf52840_dongle_merged.hex
 26b8513e76058e86a01a4b408411ce429834eb2843993eb1671f2487b160bc9a	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
-55efa672a5fce6f8757efe16a2c3444d11f8f5a575fc0fedbc2892b587c6ecac	target/nrf52840_dongle_dfu_merged.hex
+d164e7670a6a6e9b59e158d0f5357547cc205a8d34cd9e73b0232b57d4f9538f	target/nrf52840_dongle_dfu_merged.hex
 7cc558a66505e8cf8170aab50e6ddcb28f349fd7ced35ce841ccec33a533bea1	third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
-ed2beb9efd3bab6c91f7d0c6e3c1622d4920f5476844bd7551d51ba524ad4a71	target/nrf52840_mdk_dfu_merged.hex
-ee12b35c75402db7fe191f2793209c51657c98662be2b6ff41ae09bccdc7e62a	target/tab/ctap2.tab
+b6bd82285985ddbe4201ee2e37cef960d7c663a41ca5f382c5770d8c675e1f6f	target/nrf52840_mdk_dfu_merged.hex
+e338e3b091ae73f41d4663f49f539e93d5a9b3585a8bf1a791577e9486828cc1	target/tab/ctap2.tab
diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs
index 8e6c7d9c365a149d8c1b4a82e1ad019448a4ecff..d55121e312140d091eed1c794ee735b5dfb60f46 100644
--- a/src/ctap/data_formats.rs
+++ b/src/ctap/data_formats.rs
@@ -433,6 +433,9 @@ impl TryFrom<&cbor::Value> for SignatureAlgorithm {
 }
 
 // https://www.w3.org/TR/webauthn/#public-key-credential-source
+//
+// Note that we only use the WebAuthn definition as an example. This data-structure is not specified
+// by FIDO. In particular we may choose how we serialize and deserialize it.
 #[derive(Clone)]
 #[cfg_attr(test, derive(PartialEq))]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
@@ -447,25 +450,38 @@ pub struct PublicKeyCredentialSource {
     pub cred_random: Option<Vec<u8>>,
 }
 
+// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
+// is associated with a unique tag, implemented with a CBOR unsigned key.
+enum PublicKeyCredentialSourceField {
+    CredentialId = 0,
+    PrivateKey = 1,
+    RpId = 2,
+    UserHandle = 3,
+    OtherUi = 4,
+    CredRandom = 5,
+    // When a field is removed, its tag should be reserved and not used for new fields. We document
+    // those reserved tags below.
+    // Reserved tags: none.
+}
+
+impl From<PublicKeyCredentialSourceField> for cbor::KeyType {
+    fn from(field: PublicKeyCredentialSourceField) -> cbor::KeyType {
+        (field as u64).into()
+    }
+}
+
 impl From<PublicKeyCredentialSource> for cbor::Value {
     fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
+        use PublicKeyCredentialSourceField::*;
         let mut private_key = [0u8; 32];
         credential.private_key.to_bytes(&mut private_key);
-        let other_ui = match credential.other_ui {
-            None => cbor_null!(),
-            Some(other_ui) => cbor_text!(other_ui),
-        };
-        let cred_random = match credential.cred_random {
-            None => cbor_null!(),
-            Some(cred_random) => cbor_bytes!(cred_random),
-        };
-        cbor_array! {
-            credential.credential_id,
-            private_key,
-            credential.rp_id,
-            credential.user_handle,
-            other_ui,
-            cred_random,
+        cbor_map_options! {
+            CredentialId => Some(credential.credential_id),
+            PrivateKey => Some(private_key.to_vec()),
+            RpId => Some(credential.rp_id),
+            UserHandle => Some(credential.user_handle),
+            OtherUi => credential.other_ui,
+            CredRandom => credential.cred_random
         }
     }
 }
@@ -473,30 +489,36 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
 impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
     type Error = Ctap2StatusCode;
 
-    fn try_from(cbor_value: cbor::Value) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
-        use cbor::{SimpleValue, Value};
-
-        let fields = read_array(&cbor_value)?;
-        if fields.len() != 6 {
-            return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
-        }
-        let credential_id = read_byte_string(&fields[0])?;
-        let private_key = read_byte_string(&fields[1])?;
+    fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
+        use PublicKeyCredentialSourceField::*;
+        let mut map = extract_map(cbor_value)?;
+        let credential_id = extract_byte_string(ok_or_missing(map.remove(&CredentialId.into()))?)?;
+        let private_key = extract_byte_string(ok_or_missing(map.remove(&PrivateKey.into()))?)?;
         if private_key.len() != 32 {
             return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
         }
         let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32))
             .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?;
-        let rp_id = read_text_string(&fields[2])?;
-        let user_handle = read_byte_string(&fields[3])?;
-        let other_ui = match &fields[4] {
-            Value::Simple(SimpleValue::NullValue) => None,
-            cbor_value => Some(read_text_string(cbor_value)?),
-        };
-        let cred_random = match &fields[5] {
-            Value::Simple(SimpleValue::NullValue) => None,
-            cbor_value => Some(read_byte_string(cbor_value)?),
-        };
+        let rp_id = extract_text_string(ok_or_missing(map.remove(&RpId.into()))?)?;
+        let user_handle = extract_byte_string(ok_or_missing(map.remove(&UserHandle.into()))?)?;
+        let other_ui = map
+            .remove(&OtherUi.into())
+            .map(extract_text_string)
+            .transpose()?;
+        let cred_random = map
+            .remove(&CredRandom.into())
+            .map(extract_byte_string)
+            .transpose()?;
+        // We don't return whether there were unknown fields in the CBOR value. This means that
+        // deserialization is not injective. In particular deserialization is only an inverse of
+        // serialization at a given version of OpenSK. This is not a problem because:
+        // 1. When a field is deprecated, its tag is reserved and never reused in future versions,
+        //    including to be reintroduced with the same semantics. In other words, removing a field
+        //    is permanent.
+        // 2. OpenSK is never used with a more recent version of the storage. In particular, OpenSK
+        //    is never rolled-back.
+        // As a consequence, the unknown fields are only reserved fields and don't need to be
+        // preserved.
         Ok(PublicKeyCredentialSource {
             key_type: PublicKeyCredentialType::PublicKey,
             credential_id,
@@ -652,6 +674,13 @@ pub fn read_byte_string(cbor_value: &cbor::Value) -> Result<Vec<u8>, Ctap2Status
     }
 }
 
+fn extract_byte_string(cbor_value: cbor::Value) -> Result<Vec<u8>, Ctap2StatusCode> {
+    match cbor_value {
+        cbor::Value::KeyValue(cbor::KeyType::ByteString(byte_string)) => Ok(byte_string),
+        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
+    }
+}
+
 pub(super) fn read_text_string(cbor_value: &cbor::Value) -> Result<String, Ctap2StatusCode> {
     match cbor_value {
         cbor::Value::KeyValue(cbor::KeyType::TextString(text_string)) => {
@@ -661,6 +690,13 @@ pub(super) fn read_text_string(cbor_value: &cbor::Value) -> Result<String, Ctap2
     }
 }
 
+fn extract_text_string(cbor_value: cbor::Value) -> Result<String, Ctap2StatusCode> {
+    match cbor_value {
+        cbor::Value::KeyValue(cbor::KeyType::TextString(text_string)) => Ok(text_string),
+        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
+    }
+}
+
 pub(super) fn read_array(cbor_value: &cbor::Value) -> Result<&Vec<cbor::Value>, Ctap2StatusCode> {
     match cbor_value {
         cbor::Value::Array(array) => Ok(array),
@@ -677,6 +713,15 @@ pub(super) fn read_map(
     }
 }
 
+fn extract_map(
+    cbor_value: cbor::Value,
+) -> Result<BTreeMap<cbor::KeyType, cbor::Value>, Ctap2StatusCode> {
+    match cbor_value {
+        cbor::Value::Map(map) => Ok(map),
+        _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE),
+    }
+}
+
 pub(super) fn read_bool(cbor_value: &cbor::Value) -> Result<bool, Ctap2StatusCode> {
     match cbor_value {
         cbor::Value::Simple(cbor::SimpleValue::FalseValue) => Ok(false),
@@ -685,9 +730,7 @@ pub(super) fn read_bool(cbor_value: &cbor::Value) -> Result<bool, Ctap2StatusCod
     }
 }
 
-pub(super) fn ok_or_missing(
-    value_option: Option<&cbor::Value>,
-) -> Result<&cbor::Value, Ctap2StatusCode> {
+pub(super) fn ok_or_missing<T>(value_option: Option<T>) -> Result<T, Ctap2StatusCode> {
     value_option.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
 }