Compare commits

..

1 Commits

Author SHA1 Message Date
CleverWild
3db7ece6c0 refactor(custody): replace the store abstraction with direct database functions 2026-09-12 17:05:10 +02:00
4 changed files with 35 additions and 18 deletions

View File

@@ -53,7 +53,7 @@ create table if not exists operator_identity (
create unique index if not exists uniq_operator_identity_public_key on operator_identity (public_key); create unique index if not exists uniq_operator_identity_public_key on operator_identity (public_key);
create table if not exists operator ( create table if not exists operator (
id integer not null primary key references operator_identity(id) on delete restrict, -- same id as operator_identity id integer primary key references operator_identity(id) on delete restrict, -- same id as operator_identity
share blob not null, share blob not null,
share_nonce blob not null, share_nonce blob not null,

View File

@@ -1,13 +1,18 @@
//! Storage for Shamir custody material: the reconstruction threshold and the //! Storage for Shamir custody material: the reconstruction threshold and the
//! per-operator encrypted shares of the vault seal key. //! per-operator encrypted shares of the vault seal key.
//!
//! Every query lives here so that the actors above hold no Diesel code of their
//! own. The functions borrow the caller's connection instead of taking one from
//! the pool, which lets the vault write custody material inside the same
//! transaction that stores the root key.
use std::collections::HashMap; use std::collections::HashMap;
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper as _, sqlite::Sqlite}; use diesel::{ExpressionMethods as _, QueryDsl, sqlite::Sqlite};
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
use crate::db::{ use crate::db::{
models::{Operator, OperatorId, SqliteTimestamp}, models::{OperatorId, SqliteTimestamp},
schema, schema,
}; };
@@ -50,7 +55,7 @@ pub async fn write_record(
for (operator_id, share) in &record.shares { for (operator_id, share) in &record.shares {
diesel::replace_into(schema::operator::table) diesel::replace_into(schema::operator::table)
.values(( .values((
schema::operator::id.eq(*operator_id), schema::operator::id.eq(Some(*operator_id)),
schema::operator::share.eq(&share.ciphertext), schema::operator::share.eq(&share.ciphertext),
schema::operator::share_nonce.eq(&share.nonce), schema::operator::share_nonce.eq(&share.nonce),
schema::operator::share_salt.eq(&share.salt), schema::operator::share_salt.eq(&share.salt),
@@ -85,29 +90,41 @@ pub async fn threshold(conn: &mut impl AsyncConnection<Backend = Sqlite>) -> Res
.ok_or(Error::BrokenThreshold) .ok_or(Error::BrokenThreshold)
} }
/// One row of the share query: operator id, ciphertext, nonce, salt.
type ShareRow = (Option<OperatorId>, Vec<u8>, Vec<u8>, Vec<u8>);
/// Load the shares of `operators` in one query, in the order requested. /// Load the shares of `operators` in one query, in the order requested.
pub async fn shares( pub async fn shares(
conn: &mut impl AsyncConnection<Backend = Sqlite>, conn: &mut impl AsyncConnection<Backend = Sqlite>,
operators: &[OperatorId], operators: &[OperatorId],
) -> Result<Vec<EncryptedShare>, Error> { ) -> Result<Vec<EncryptedShare>, Error> {
let rows: Vec<Operator> = schema::operator::table let wanted: Vec<Option<OperatorId>> = operators.iter().copied().map(Some).collect();
.filter(schema::operator::id.eq_any(operators))
.select(Operator::as_select()) let rows: Vec<ShareRow> = schema::operator::table
.filter(schema::operator::id.eq_any(wanted))
.select((
schema::operator::id,
schema::operator::share,
schema::operator::share_nonce,
schema::operator::share_salt,
))
.load(conn) .load(conn)
.await?; .await?;
let mut found: HashMap<OperatorId, EncryptedShare> = rows let mut found: HashMap<OperatorId, EncryptedShare> = rows
.into_iter() .into_iter()
.map(|row| { .filter_map(|(id, ciphertext, nonce, salt)| {
id.map(|id| {
( (
row.id, id,
EncryptedShare { EncryptedShare {
ciphertext: row.share, ciphertext,
nonce: row.share_nonce, nonce,
salt: row.share_salt, salt,
}, },
) )
}) })
})
.collect(); .collect();
operators operators

View File

@@ -292,7 +292,7 @@ pub struct OperatorClient {
pub updated_at: SqliteTimestamp, pub updated_at: SqliteTimestamp,
} }
#[derive(Queryable, Debug, Selectable)] #[derive(Queryable, Debug)]
#[diesel(table_name = schema::operator, check_for_backend(Sqlite))] #[diesel(table_name = schema::operator, check_for_backend(Sqlite))]
pub struct Operator { pub struct Operator {
pub id: OperatorId, pub id: OperatorId,

View File

@@ -155,7 +155,7 @@ diesel::table! {
diesel::table! { diesel::table! {
operator (id) { operator (id) {
id -> Integer, id -> Nullable<Integer>,
share -> Binary, share -> Binary,
share_nonce -> Binary, share_nonce -> Binary,
share_salt -> Binary, share_salt -> Binary,