Skip to content

Commit 4818bbc

Browse files
committed
feat(keystore): use a hash for the MlsPendingMessage entity key
Originally I'd hoped we could eliminate a non-unit key entirely, but it turns out that we cannot. So a better solution is to compute a fast deterministic hash of the content. This is more space-efficient than run-length-encoding for obvious reasons, and also avoids keeping copies of sensitive data in more places in memory than are strictly required. Given 128 bits of hashed data, the chance of a collision should be infinitesimal.
1 parent afe9ca9 commit 4818bbc

File tree

1 file changed

+16
-39
lines changed

1 file changed

+16
-39
lines changed

keystore/src/entities/mls.rs

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use zeroize::{Zeroize, ZeroizeOnDrop};
1+
use zeroize::Zeroize;
22

33
use crate::{
44
CryptoKeystoreResult, Sha256Hash,
@@ -89,59 +89,36 @@ impl<'a> KeyType for ConversationId<'a> {
8989
}
9090

9191
/// [`MlsPendingMessage`]s have no distinct primary key;
92-
/// they must always be accessed via [`MlsPendingMessage::find_all_by_conversation_id`] and
93-
/// cleaned up with [`MlsPendingMessage::delete_by_conversation_id`]
92+
/// they must always be accessed via the [`SearchableEntity`][crate::traits::SearchableEntity] and
93+
/// [`DeletableBySearchKey`][crate::traits::DeletableBySearchKey] traits.
9494
///
95-
/// However, we have to fake a primary key type in order to support
96-
/// `KeystoreTransaction::remove_pending_messages_by_conversation_id`. Additionally we need the same one in WASM, where
97-
/// it's necessary for item-level encryption.
95+
/// However the keystore's support of internal transactions demands a primary key:
96+
/// ultimately that structure boils down to `Map<CollectionName, Map<PrimaryKey, Entity>>`, so anything other
97+
/// than a full primary key just breaks things.
9898
///
99-
/// This implementation is fairly inefficient and hopefully temporary. But it at least implements the correct semantics.
100-
#[derive(ZeroizeOnDrop)]
101-
pub struct MlsPendingMessagePrimaryKey {
102-
pub(crate) foreign_id: Vec<u8>,
103-
message: Vec<u8>,
104-
}
99+
/// We use `xxhash3` as a fast hash implementation, and take 128 bits of hash to ensure
100+
/// that the chance of a collision is effectively 0.
101+
pub struct MlsPendingMessagePrimaryKey(u128);
105102

106103
impl From<&MlsPendingMessage> for MlsPendingMessagePrimaryKey {
107104
fn from(value: &MlsPendingMessage) -> Self {
108-
Self {
109-
foreign_id: value.foreign_id.clone(),
110-
message: value.message.clone(),
111-
}
105+
let mut hasher = twox_hash::xxhash3_128::Hasher::new();
106+
hasher.write(&value.foreign_id);
107+
hasher.write(&value.message);
108+
Self(hasher.finish_128())
112109
}
113110
}
114111

115112
impl KeyType for MlsPendingMessagePrimaryKey {
116113
fn bytes(&self) -> std::borrow::Cow<'_, [u8]> {
117-
// run-length encoding: 32 bits of size for each field, followed by the field
118-
let fields = [&self.foreign_id, &self.message];
119-
let mut key = Vec::with_capacity(
120-
((u32::BITS / u8::BITS) as usize * fields.len()) + self.foreign_id.len() + self.message.len(),
121-
);
122-
for field in fields {
123-
key.extend((field.len() as u32).to_le_bytes());
124-
key.extend(field.as_slice());
125-
}
126-
key.into()
114+
self.0.to_be_bytes().as_slice().to_owned().into()
127115
}
128116
}
129117

130118
impl OwnedKeyType for MlsPendingMessagePrimaryKey {
131119
fn from_bytes(bytes: &[u8]) -> Option<Self> {
132-
// run-length decoding: 32 bits of size for each field, followed by the field
133-
let (len, bytes) = bytes.split_at_checked(4)?;
134-
let len = u32::from_le_bytes(len.try_into().ok()?);
135-
let (foreign_id, bytes) = bytes.split_at_checked(len as _)?;
136-
137-
let (len, bytes) = bytes.split_at_checked(4)?;
138-
let len = u32::from_le_bytes(len.try_into().ok()?);
139-
let (message, bytes) = bytes.split_at_checked(len as _)?;
140-
141-
bytes.is_empty().then(|| Self {
142-
foreign_id: foreign_id.to_owned(),
143-
message: message.to_owned(),
144-
})
120+
let array = bytes.try_into().ok()?;
121+
Some(Self(u128::from_be_bytes(array)))
145122
}
146123
}
147124

0 commit comments

Comments
 (0)