Skip to content

Commit 5c188e8

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 32 bytes of hashed data, the chance of a collision should be infinitesimal.
1 parent 460ea58 commit 5c188e8

File tree

1 file changed

+19
-39
lines changed

1 file changed

+19
-39
lines changed

keystore/src/entities/mls.rs

Lines changed: 19 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,39 @@ 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 `blake3` as a fast cryptographically-secure hash implementation, and take 32 bytes of hash to ensure
100+
/// that the chance of a collision is effectively 0.
101+
pub struct MlsPendingMessagePrimaryKey([u8; 32]);
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+
Self(
106+
blake3::Hasher::new()
107+
.update(&value.foreign_id)
108+
.update(&value.message)
109+
.finalize()
110+
.into(),
111+
)
112112
}
113113
}
114114

115115
impl KeyType for MlsPendingMessagePrimaryKey {
116116
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()
117+
(&self.0).into()
127118
}
128119
}
129120

130121
impl OwnedKeyType for MlsPendingMessagePrimaryKey {
131122
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-
})
123+
let array = bytes.try_into().ok()?;
124+
Some(Self(array))
145125
}
146126
}
147127

0 commit comments

Comments
 (0)