Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
85f22d5
feat(keystore): introduce `trait OwnedKeyType`
coriolinus Dec 17, 2025
44bbf95
refactor(keystore): formally require that primary key be `OwnedKeyType`
coriolinus Dec 17, 2025
7c6b1e8
feat(keystore): complete the `FetchFromDatabase` API
coriolinus Dec 18, 2025
9f9ce0a
refactor(keystore): rework the dynamic dispatch module
coriolinus Dec 17, 2025
2975abb
refactor(keystore): hold onto entity items in dynamic dispatch
coriolinus Dec 18, 2025
291336d
refactor(keystore): simplify parameter type for borrowing trait methods
coriolinus Dec 18, 2025
807e790
chore(keystore): improve trait item docs
coriolinus Dec 18, 2025
7c2fed6
chore(keystore): adjust old `UniqueEntity` trait to still compile
coriolinus Dec 19, 2025
9a3893c
chore(keystore): update old blanket `impl Entity for UniqueEntity`
coriolinus Dec 19, 2025
3aa9d5c
chore(keystore): impl `BorrowPrimaryKey` for `MlsPendingMessage`
coriolinus Dec 18, 2025
86849c3
chore(keystore): adjust impls of `to_transaction_entity`
coriolinus Dec 18, 2025
d458597
chore(keystore): update entity impls to track new trait requirements
coriolinus Dec 19, 2025
79ff199
chore(keystore): rewrite `PersistedMlsGroupExt` in terms of new traits
coriolinus Dec 19, 2025
df375cd
chore(keystore): update `proteus_traits::PreKeyStore` impl
coriolinus Dec 19, 2025
c9edf61
chore(keystore): update mls-specific trait impls
coriolinus Dec 19, 2025
a9c5cd9
chore(keystore): add `EntityId` constructor helpers
coriolinus Dec 19, 2025
70f337f
chore(keystore): update transaction impls
coriolinus Dec 19, 2025
b1582a5
chore(keystore): update `trait FetchFromDatabase` for coherency
coriolinus Dec 19, 2025
fcbcc18
chore(keystore): update proteus entity impls to use new traits
coriolinus Dec 19, 2025
df7fa6b
chore(keystore): update `Database` impls to use new traits
coriolinus Dec 19, 2025
3830119
chore(keystore): `cargo +nightly fmt`
coriolinus Dec 19, 2025
f3db970
refactor(keystore): break out primary key from entity type
coriolinus Dec 19, 2025
823296e
chore(keystore): update all entities to new traits
coriolinus Dec 19, 2025
21f3dbb
chore(keystore): ensure wasm stuff is also updated
coriolinus Dec 19, 2025
b62eb8d
chore: adjust other crates to use new `FetchFromDatabase`
coriolinus Dec 19, 2025
f687d56
chore(keystore/tests): rollback on drop, not commit
coriolinus Dec 19, 2025
f237bfe
chore(crypto-macros): preserve `no-upsert` error behavior
coriolinus Dec 19, 2025
4a94c94
chore(keystore/tests): restore stateful behavior within z-entities tests
coriolinus Dec 19, 2025
1f1474b
fixup! chore(keystore/tests): rollback on drop, not commit
coriolinus Dec 19, 2025
61dc48a
chore(keystore): use correct primary key for mls types
coriolinus Dec 19, 2025
7e607b9
chore: adjust other crates to use new `FetchFromDatabase`
coriolinus Dec 22, 2025
a08053e
chore(crypto-macros): sql data type for hex fields is string
coriolinus Dec 22, 2025
1274cad
chore: add test demonstrating storing and loading keypackages
coriolinus Dec 22, 2025
efa9773
chore(crypto-macros): search by hex value of key not key when appropr…
coriolinus Dec 22, 2025
1842952
chore(keystore): add z_entities test for `E2eiAcmeCa`
coriolinus Dec 22, 2025
051202d
chore(CC): improve `fn e2ei_register_acme_ca`
coriolinus Dec 22, 2025
dec9c5b
chore(CC): improve group-from-welcome error mapping
coriolinus Dec 22, 2025
614be92
chore(keystore): query proteus identities properly
coriolinus Dec 22, 2025
449db46
chore(keystore): when saving entities remove their id from the delete…
coriolinus Dec 22, 2025
94ae4ef
chore(keystore): handle `MlsPendingMessage`s properly
coriolinus Dec 23, 2025
5fb34da
chore(keystore): handle keys which are not byte vectors
coriolinus Dec 23, 2025
f483541
fixup! chore(keystore): handle `MlsPendingMessage`s properly
coriolinus Dec 23, 2025
4bd9635
fixup! chore(crypto-macros): search by hex value of key not key when …
coriolinus Dec 23, 2025
3472eac
fixup! chore(keystore): handle `MlsPendingMessage`s properly
coriolinus Dec 23, 2025
f032f1c
chore(crypto-macros): properly read optional types from sql
coriolinus Dec 23, 2025
df37aa1
chore(keystore): don't panic if an entity with unknown id is used
coriolinus Dec 23, 2025
024a454
chore(keystore): `ConsumerData` is a real entity
coriolinus Dec 23, 2025
fff17b2
chore(keystore): fix loading of generic unique entities
coriolinus Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crypto-macros/src/entity_derive/derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl KeyStoreEntityFlattened {
}

fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity {
crate::transaction::dynamic_dispatch::Entity::#struct_name(self)
crate::transaction::dynamic_dispatch::Entity::#struct_name(self.into())
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion crypto-macros/src/entity_derive_new/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ where
pub(super) fn load_expression(&self) -> TokenStream {
let column_name = self.sql_name();

let expr = quote!(row.get::<_, Vec<u8>>(#column_name)?);
let sql_data_type = match self.transformation {
None => self.column_type.get_as_type(),
Some(FieldTransformation::Hex) => quote!(String),
};
let expr = quote!(row.get::<_, #sql_data_type>(#column_name)?);

let expr = match self.transformation {
None => expr,
Expand Down
20 changes: 15 additions & 5 deletions crypto-macros/src/entity_derive_new/column_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ impl TryFrom<Type> for ColumnType {
pub(super) trait EmitGetExpression {
/// Emit an expression which wraps the input expression, appropriately parsing according to this column type.
fn emit_get_expression(&self, input: TokenStream) -> TokenStream;

/// Emit an expression with the rust type which should be used in the rusqlite `get` expression
fn get_as_type(&self) -> TokenStream;
}

impl EmitGetExpression for IdColumnType {
Expand All @@ -142,17 +145,24 @@ impl EmitGetExpression for IdColumnType {
Self::String => quote!(String::from_utf8(#input).map_err(|err| err.utf8_error())?),
}
}

fn get_as_type(&self) -> TokenStream {
quote!(Vec<u8>)
}
}

impl EmitGetExpression for ColumnType {
fn emit_get_expression(&self, input: TokenStream) -> TokenStream {
match self {
ColumnType::Bytes => input,
ColumnType::Bytes | ColumnType::OptionalBytes => input,
ColumnType::String => quote!(String::from_utf8(#input).map_err(|err| err.utf8_error())?),
ColumnType::OptionalBytes => quote! {{
let data = #input;
(!data.is_empty()).then_some(data)
}},
}
}

fn get_as_type(&self) -> TokenStream {
match self {
ColumnType::Bytes | ColumnType::String => quote!(Vec<u8>),
ColumnType::OptionalBytes => quote!(Option<Vec<u8>>),
}
}
}
122 changes: 72 additions & 50 deletions crypto-macros/src/entity_derive_new/derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Ident, Lifetime};

use crate::entity_derive_new::{Entity, column_type::ColumnType};
use crate::entity_derive_new::{Entity, FieldTransformation, column_type::ColumnType};

impl quote::ToTokens for Entity {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.impl_entity_base());
tokens.extend(self.impl_primary_key());
tokens.extend(self.impl_entity_generic());
tokens.extend(self.impl_entity_wasm());
tokens.extend(self.impl_borrow_primary_key());
tokens.extend(self.impl_entity_get_borrowed());
tokens.extend(self.impl_entity_database_mutation());
tokens.extend(self.impl_entity_delete_borrowed());
tokens.extend(self.impl_decrypting());
Expand All @@ -37,7 +38,36 @@ impl Entity {
const COLLECTION_NAME: &'static str = #collection_name;

fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity {
crate::transaction::dynamic_dispatch::Entity::#struct_name(self)
crate::transaction::dynamic_dispatch::Entity::#struct_name(self.into())
}
}
}
}

/// `impl PrimaryKey for MyEntity` and `impl BorrowPrimaryKey for MyEntity`
fn impl_primary_key(&self) -> TokenStream {
let Self {
struct_name, id_column, ..
} = self;

let primary_key = id_column.column_type.owned();
let borrowed_primary_key = id_column.column_type.borrowed();
let pk_field_name = &id_column.field_name;

quote! {
impl crate::traits::PrimaryKey for #struct_name {
type PrimaryKey = #primary_key;

fn primary_key(&self) -> Self::PrimaryKey {
self.#pk_field_name.clone()
}
}

impl crate::traits::BorrowPrimaryKey for #struct_name {
type BorrowedPrimaryKey = #borrowed_primary_key;

fn borrow_primary_key(&self) -> &Self::BorrowedPrimaryKey {
&self.#pk_field_name
}
}
}
Expand All @@ -52,24 +82,15 @@ impl Entity {
..
} = self;

let primary_key = id_column.column_type.owned();
let id_field_name = &id_column.field_name;

let field_assignments = std::iter::once(id_column.field_assignment())
.chain(other_columns.iter().map(|column| column.field_assignment()));

quote! {
#[cfg(not(target_family = "wasm"))]
#[::async_trait::async_trait]
impl crate::traits::Entity for #struct_name {
type PrimaryKey = #primary_key;

fn primary_key(&self) -> #primary_key {
self.#id_field_name.clone()
}

async fn get(conn: &mut Self::ConnectionType, key: &Self::PrimaryKey) -> crate::CryptoKeystoreResult<Option<Self>> {
<Self as crate::traits::BorrowPrimaryKey>::get_borrowed(conn, key).await
<Self as crate::traits::EntityGetBorrowed>::get_borrowed(conn, key).await
}

async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<u32> {
Expand All @@ -89,25 +110,14 @@ impl Entity {

/// `#[cfg(target_family = "wasm")] impl Entity for MyEntity`
fn impl_entity_wasm(&self) -> TokenStream {
let Self {
struct_name, id_column, ..
} = self;

let primary_key = id_column.column_type.owned();
let id_field_name = &id_column.field_name;
let Self { struct_name, .. } = self;

quote! {
#[cfg(target_family = "wasm")]
#[::async_trait::async_trait(?Send)]
impl crate::traits::Entity for #struct_name {
type PrimaryKey = #primary_key;

fn primary_key(&self) -> #primary_key {
self.#id_field_name.clone()
}

async fn get(conn: &mut Self::ConnectionType, key: &Self::PrimaryKey) -> crate::CryptoKeystoreResult<Option<Self>> {
<Self as crate::traits::BorrowPrimaryKey>::get_borrowed(conn, key).await
<Self as crate::traits::EntityGetBorrowed>::get_borrowed(conn, key).await
}

async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<u32> {
Expand All @@ -121,17 +131,15 @@ impl Entity {
}
}

/// `impl BorrowPrimaryKey for MyEntity`
fn impl_borrow_primary_key(&self) -> TokenStream {
/// `impl EntityGetBorrowed for MyEntity`
fn impl_entity_get_borrowed(&self) -> TokenStream {
let Self {
struct_name,
id_column,
other_columns,
..
} = self;

let borrowed_primary_key = id_column.column_type.borrowed();
let pk_field_name = &id_column.field_name;
let pk_column_name = id_column
.column_name
.clone()
Expand All @@ -140,29 +148,31 @@ impl Entity {
let field_assignments = std::iter::once(id_column.field_assignment())
.chain(other_columns.iter().map(|column| column.field_assignment()));

// if we ever add a second field transformation, we'll want this match pattern
#[allow(clippy::manual_map)]
let key_transform = match id_column.transformation {
None => None,
Some(FieldTransformation::Hex) => Some(quote! {let key = hex::encode(key);}),
};

quote! {
#[cfg_attr(target_family = "wasm", ::async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), ::async_trait::async_trait)]
impl crate::traits::BorrowPrimaryKey for #struct_name {
type BorrowedPrimaryKey = #borrowed_primary_key;

fn borrow_primary_key(&self) -> &Self::BorrowedPrimaryKey {
&self.#pk_field_name
}

async fn get_borrowed<Q>(conn: &mut Self::ConnectionType, key: &Q) -> crate::CryptoKeystoreResult<Option<Self>>
where
Self::PrimaryKey: std::borrow::Borrow<Q>,
Q: crate::traits::KeyType,
impl crate::traits::EntityGetBorrowed for #struct_name {
async fn get_borrowed(conn: &mut Self::ConnectionType, key: &Self::BorrowedPrimaryKey)
-> crate::CryptoKeystoreResult<Option<Self>>
{
let key = <&Self::BorrowedPrimaryKey as crate::traits::KeyType>::bytes(&key);
let key = key.as_ref();
#key_transform
#[cfg(target_family = "wasm")]
{
conn.storage().new_get(key.bytes().as_ref()).await
conn.storage().new_get(key).await
}

#[cfg(not(target_family = "wasm"))]
{
crate::entities::platform::get_helper::<Self, _>(conn, #pk_column_name, key.bytes().as_ref(), |row| {
crate::entities::platform::get_helper::<Self, _>(conn, #pk_column_name, key, |row| {
Ok(Self {
#( #field_assignments, )*
})
Expand Down Expand Up @@ -196,6 +206,10 @@ impl Entity {
.map(|tokens| quote!(#tokens,))
.collect::<TokenStream>();

let sql_map_err = (!upsert).then_some(quote! {
.map_err(|_| CryptoKeystoreError::AlreadyExists(Self::COLLECTION_NAME))
});

quote! {
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
Expand All @@ -211,7 +225,7 @@ impl Entity {
#[cfg(not(target_family = "wasm"))]
{
let mut stmt = tx.prepare_cached(#sql_statement)?;
stmt.execute(rusqlite::params![#fields])?;
stmt.execute(rusqlite::params![#fields])#sql_map_err?;
Ok(())
}
}
Expand Down Expand Up @@ -241,27 +255,35 @@ impl Entity {
} = self;

let id_column_name = id_column.sql_name();
// if we ever add a second field transformation, we'll want this match pattern
#[allow(clippy::manual_map)]
let key_transform = match id_column.transformation {
None => None,
Some(FieldTransformation::Hex) => Some(quote! {let key = hex::encode(key);}),
};

quote! {
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
impl<'a> crate::traits::EntityDeleteBorrowed<'a> for #struct_name {
async fn delete_borrowed<Q>(
async fn delete_borrowed(
tx: &<Self as crate::traits::EntityDatabaseMutation<'a>>::Transaction,
id: &Q,
id: &<Self as crate::traits::BorrowPrimaryKey>::BorrowedPrimaryKey,
) -> crate::CryptoKeystoreResult<bool>
where
Self::PrimaryKey: std::borrow::Borrow<Q>,
Q: crate::traits::KeyType
for<'pk> &'pk <Self as crate::traits::BorrowPrimaryKey>::BorrowedPrimaryKey: crate::traits::KeyType,
{
let key = <&<Self as crate::traits::BorrowPrimaryKey>::BorrowedPrimaryKey as crate::traits::KeyType>::bytes(&id);
let key = key.as_ref();
#key_transform
#[cfg(target_family = "wasm")]
{
tx.new_delete::<Self>(id.bytes().as_ref()).await
tx.new_delete::<Self>(key).await
}

#[cfg(not(target_family = "wasm"))]
{
crate::entities::platform::delete_helper::<Self>(tx, #id_column_name, id.bytes().as_ref()).await
crate::entities::platform::delete_helper::<Self>(tx, #id_column_name, key).await
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions crypto/src/e2e_identity/pki_env.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::collections::HashSet;

use core_crypto_keystore::{
connection::FetchFromDatabase,
entities::{E2eiAcmeCA, E2eiCrl, E2eiIntermediateCert},
traits::FetchFromDatabase,
};
use wire_e2e_identity::prelude::x509::revocation::{PkiEnvironment, PkiEnvironmentParams};
use x509_cert::der::Decode;
Expand Down Expand Up @@ -33,7 +33,7 @@ impl IntoIterator for NewCrlDistributionPoints {

pub(crate) async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<Option<PkiEnvironment>> {
let mut trust_roots = vec![];
let Ok(ta_raw) = data_provider.find_unique::<E2eiAcmeCA>().await else {
let Ok(Some(ta_raw)) = data_provider.get_unique::<E2eiAcmeCA>().await else {
return Ok(None);
};

Expand All @@ -42,15 +42,15 @@ pub(crate) async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> R
);

let intermediates = data_provider
.find_all::<E2eiIntermediateCert>(Default::default())
.load_all::<E2eiIntermediateCert>()
.await
.map_err(KeystoreError::wrap("finding intermediate certificates"))?
.into_iter()
.map(|inter| x509_cert::Certificate::from_der(&inter.content))
.collect::<Result<Vec<_>, _>>()?;

let crls = data_provider
.find_all::<E2eiCrl>(Default::default())
.load_all::<E2eiCrl>()
.await
.map_err(KeystoreError::wrap("finding crls"))?
.into_iter()
Expand Down
4 changes: 2 additions & 2 deletions crypto/src/group_store.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use core_crypto_keystore::connection::FetchFromDatabase;
use core_crypto_keystore::traits::FetchFromDatabase;

use crate::{ConversationId, KeystoreError, MlsConversation, RecursiveError, Result};

Expand Down Expand Up @@ -31,7 +31,7 @@ impl GroupStoreEntity for MlsConversation {
keystore: &impl FetchFromDatabase,
) -> crate::Result<Option<Self>> {
let result = keystore
.find::<Self::RawStoreValue>(id)
.get_borrowed::<Self::RawStoreValue>(id.as_ref())
.await
.map_err(KeystoreError::wrap("finding mls conversation from keystore by id"))?;
let Some(store_value) = result else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core_crypto_keystore::{connection::FetchFromDatabase as _, entities::StoredBufferedCommit};
use core_crypto_keystore::{entities::StoredBufferedCommit, traits::FetchFromDatabase as _};
use log::info;
use openmls::framing::MlsMessageIn;
use openmls_traits::OpenMlsCryptoProvider as _;
Expand Down Expand Up @@ -34,7 +34,7 @@ impl ConversationGuard {
self.crypto_provider()
.await?
.keystore()
.find::<StoredBufferedCommit>(conversation.id())
.get_borrowed::<StoredBufferedCommit>(conversation.id().as_ref())
.await
.map(|option| option.map(StoredBufferedCommit::into_commit_data))
.map_err(KeystoreError::wrap("attempting to retrieve buffered commit"))
Expand Down Expand Up @@ -69,7 +69,7 @@ impl ConversationGuard {
self.crypto_provider()
.await?
.keystore()
.remove::<StoredBufferedCommit, _>(conversation.id())
.remove_borrowed::<StoredBufferedCommit>(conversation.id().as_ref())
.await
.map_err(KeystoreError::wrap("attempting to clear buffered commit"))
.map_err(Into::into)
Expand Down
2 changes: 1 addition & 1 deletion crypto/src/mls/conversation/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl MlsConversation {
// ..so if there's any, we clear them after the commit is merged
for oln in &previous_own_leaf_nodes {
let ek = oln.encryption_key().as_slice();
let _ = backend.key_store().remove::<StoredEncryptionKeyPair, _>(ek).await;
let _ = backend.key_store().remove_borrowed::<StoredEncryptionKeyPair>(ek).await;
}

client
Expand Down
Loading
Loading