Compare commits
3 Commits
f074a4f00b
...
9dbb18ae82
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dbb18ae82 | ||
|
|
a773255935 | ||
|
|
3f801abdff |
@@ -5,7 +5,8 @@ package arbiter.shared;
|
|||||||
enum VaultState {
|
enum VaultState {
|
||||||
VAULT_STATE_UNSPECIFIED = 0;
|
VAULT_STATE_UNSPECIFIED = 0;
|
||||||
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
||||||
VAULT_STATE_SEALED = 2;
|
VAULT_STATE_BOOSTRAPPING = 2;
|
||||||
VAULT_STATE_UNSEALED = 3;
|
VAULT_STATE_SEALED = 3;
|
||||||
VAULT_STATE_ERROR = 4;
|
VAULT_STATE_UNSEALED = 4;
|
||||||
|
VAULT_STATE_ERROR = 5;
|
||||||
}
|
}
|
||||||
|
|||||||
656
server/Cargo.lock
generated
656
server/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ resolver = "3"
|
|||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
alloy = "2.0.0"
|
alloy = "2.0.4"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = { version = "0.4.44", features = ["serde"] }
|
chrono = { version = "0.4.44", features = ["serde"] }
|
||||||
@@ -16,15 +16,15 @@ kameo = {git = "https://github.com/hdbg/kameo.git", rev = "805b417"}
|
|||||||
kameo_actors = {git = "https://github.com/hdbg/kameo.git", rev = "805b417"}
|
kameo_actors = {git = "https://github.com/hdbg/kameo.git", rev = "805b417"}
|
||||||
hmac = "0.13.0"
|
hmac = "0.13.0"
|
||||||
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
||||||
ml-dsa = { version = "0.1.0-rc.8", features = ["zeroize"] }
|
ml-dsa = { version = "0.1.0-rc.9", features = ["zeroize"] }
|
||||||
mutants = "0.0.4"
|
mutants = "0.0.4"
|
||||||
prost = "0.14.3"
|
prost = "0.14.3"
|
||||||
prost-types = { version = "0.14.3", features = ["chrono"] }
|
prost-types = { version = "0.14.3", features = ["chrono"] }
|
||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
rcgen = { version = "0.14.7", features = [ "aws_lc_rs", "pem", "x509-parser", "zeroize" ], default-features = false }
|
rcgen = { version = "0.14.7", features = [ "aws_lc_rs", "pem", "x509-parser", "zeroize" ], default-features = false }
|
||||||
rstest = "0.26.1"
|
rstest = "0.26.1"
|
||||||
rustls = { version = "0.23.38", features = ["aws-lc-rs", "logging", "prefer-post-quantum", "std"], default-features = false }
|
rustls = { version = "0.23.40", features = ["aws-lc-rs", "logging", "prefer-post-quantum", "std"], default-features = false }
|
||||||
rustls-pki-types = "1.14.0"
|
rustls-pki-types = "1.14.1"
|
||||||
sha2 = "0.11"
|
sha2 = "0.11"
|
||||||
smlang = "0.8.0"
|
smlang = "0.8.0"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ tokio.workspace = true
|
|||||||
tokio-stream.workspace = true
|
tokio-stream.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
http = "1.4.0"
|
http = "1.4.0"
|
||||||
rustls-webpki = { version = "0.103.12", features = ["aws-lc-rs"] }
|
rustls-webpki = { version = "0.103.13", features = ["aws-lc-rs"] }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub trait SafeCellHandle<T> {
|
|||||||
fn read(&mut self) -> Self::CellRead<'_>;
|
fn read(&mut self) -> Self::CellRead<'_>;
|
||||||
fn write(&mut self) -> Self::CellWrite<'_>;
|
fn write(&mut self) -> Self::CellWrite<'_>;
|
||||||
|
|
||||||
fn new_inline<F>(f: F) -> Self
|
fn new_inline_default<F>(f: F) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
T: Default,
|
T: Default,
|
||||||
@@ -36,6 +36,14 @@ pub trait SafeCellHandle<T> {
|
|||||||
cell
|
cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_inline<F>(f: Box<F>) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
F: for<'a> FnOnce() -> T,
|
||||||
|
{
|
||||||
|
Self::new(f())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_inline<F, R>(&mut self, f: F) -> R
|
fn read_inline<F, R>(&mut self, f: F) -> R
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ license = "Apache-2.0"
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
diesel = { version = "2.3.7", features = ["chrono", "returning_clauses_for_sqlite_3_35", "serde_json", "time", "uuid"] }
|
diesel = { version = "2.3.9", features = ["chrono", "returning_clauses_for_sqlite_3_35", "serde_json", "time", "uuid"] }
|
||||||
diesel-async = { version = "0.8.0", features = [
|
diesel-async = { version = "0.9.0", features = [
|
||||||
"bb8",
|
"bb8",
|
||||||
"migrations",
|
"migrations",
|
||||||
"sqlite",
|
"sqlite",
|
||||||
@@ -27,7 +27,7 @@ tokio.workspace = true
|
|||||||
rustls.workspace = true
|
rustls.workspace = true
|
||||||
smlang.workspace = true
|
smlang.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
diesel_migrations = { version = "2.3.2", features = ["sqlite"] }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
tokio-stream.workspace = true
|
tokio-stream.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
@@ -50,7 +50,7 @@ subtle = "2.6.1"
|
|||||||
x25519-dalek.workspace = true
|
x25519-dalek.workspace = true
|
||||||
k256.workspace = true
|
k256.workspace = true
|
||||||
kameo_actors.workspace = true
|
kameo_actors.workspace = true
|
||||||
blahaj = "0.6.0"
|
vsss-rs = "5.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "1.11.0"
|
proptest = "1.11.0"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ create table if not exists operator_identity (
|
|||||||
created_at integer not null default(unixepoch ('now')),
|
created_at integer not null default(unixepoch ('now')),
|
||||||
updated_at integer not null default(unixepoch ('now'))
|
updated_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
create unique index if not exists uniq_operator_client_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 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
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl Bootstrapper {
|
|||||||
let row_count: i64 = {
|
let row_count: i64 = {
|
||||||
let mut conn = db.get().await?;
|
let mut conn = db.get().await?;
|
||||||
|
|
||||||
schema::operator_identity::table
|
schema::operator::table
|
||||||
.count()
|
.count()
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::{
|
crypto::{
|
||||||
KeyCell, derive_key,
|
KeyCell, derive_key,
|
||||||
@@ -6,7 +8,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
models::{self, RootKeyHistory, RootKeyHistoryId},
|
models::{self, OperatorId, OperatorIdentityId, RootKeyHistory, RootKeyHistoryId},
|
||||||
schema::{self},
|
schema::{self},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -15,10 +17,11 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
ExpressionMethods as _, OptionalExtension, QueryDsl, SelectableHelper,
|
ExpressionMethods as _, OptionalExtension, QueryDsl, SelectableHelper,
|
||||||
dsl::{insert_into, update},
|
dsl::{count, insert_into, update},
|
||||||
|
select,
|
||||||
};
|
};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
use hmac::{KeyInit as _, Mac as _};
|
use hmac::{KeyInit as _, Mac as _, digest::common};
|
||||||
use kameo::{Actor, Reply, actor::ActorRef, messages};
|
use kameo::{Actor, Reply, actor::ActorRef, messages};
|
||||||
use kameo_actors::message_bus::{MessageBus, Publish};
|
use kameo_actors::message_bus::{MessageBus, Publish};
|
||||||
use strum::{EnumDiscriminants, IntoDiscriminant};
|
use strum::{EnumDiscriminants, IntoDiscriminant};
|
||||||
@@ -62,6 +65,15 @@ pub enum Error {
|
|||||||
BrokenDatabase,
|
BrokenDatabase,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum UnsealError {}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum BootstrapError {
|
||||||
|
#[error("That operator already contributed his share")]
|
||||||
|
AlreadyContributed,
|
||||||
|
}
|
||||||
|
|
||||||
struct Unsealed {
|
struct Unsealed {
|
||||||
root_key_history_id: RootKeyHistoryId,
|
root_key_history_id: RootKeyHistoryId,
|
||||||
root_key: KeyCell,
|
root_key: KeyCell,
|
||||||
@@ -73,8 +85,15 @@ enum State {
|
|||||||
#[default]
|
#[default]
|
||||||
Unbootstrapped,
|
Unbootstrapped,
|
||||||
|
|
||||||
|
Bootstrapping {
|
||||||
|
declared_operators: u64,
|
||||||
|
current_passphrases: HashMap<OperatorIdentityId, SafeCell<Vec<u8>>>,
|
||||||
|
},
|
||||||
|
|
||||||
Sealed {
|
Sealed {
|
||||||
|
threshold: u64, // basically, quorum size
|
||||||
root_key_history_id: RootKeyHistoryId,
|
root_key_history_id: RootKeyHistoryId,
|
||||||
|
current_shares: HashMap<OperatorId, SafeCell<Vec<u8>>>,
|
||||||
},
|
},
|
||||||
Unsealed(Unsealed),
|
Unsealed(Unsealed),
|
||||||
}
|
}
|
||||||
@@ -90,7 +109,6 @@ pub struct Vault {
|
|||||||
events: ActorRef<MessageBus>,
|
events: ActorRef<MessageBus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
|
||||||
impl Vault {
|
impl Vault {
|
||||||
pub async fn new(db: db::DatabasePool, events: ActorRef<MessageBus>) -> Result<Self, Error> {
|
pub async fn new(db: db::DatabasePool, events: ActorRef<MessageBus>) -> Result<Self, Error> {
|
||||||
let state = {
|
let state = {
|
||||||
@@ -103,9 +121,17 @@ impl Vault {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
match root_key_history {
|
match root_key_history {
|
||||||
Some(root_key_history) => State::Sealed {
|
Some(root_key_history) => {
|
||||||
root_key_history_id: root_key_history.id,
|
let operator_count: i64 = schema::operator::table
|
||||||
},
|
.count()
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await?;
|
||||||
|
State::Sealed {
|
||||||
|
root_key_history_id: root_key_history.id,
|
||||||
|
current_shares: HashMap::default(),
|
||||||
|
threshold: shamir_threshold(operator_count.cast_unsigned()), // invariant: db couldn't return negative number of rows
|
||||||
|
}
|
||||||
|
}
|
||||||
None => State::Unbootstrapped,
|
None => State::Unbootstrapped,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -122,31 +148,29 @@ impl Vault {
|
|||||||
let mut conn = pool.get().await?;
|
let mut conn = pool.get().await?;
|
||||||
|
|
||||||
let nonce = conn
|
let nonce = conn
|
||||||
.exclusive_transaction(|conn| {
|
.exclusive_transaction(async |conn| {
|
||||||
Box::pin(async move {
|
let current_nonce: Vec<u8> = schema::root_key_history::table
|
||||||
let current_nonce: Vec<u8> = schema::root_key_history::table
|
.filter(schema::root_key_history::id.eq(root_key_id))
|
||||||
.filter(schema::root_key_history::id.eq(root_key_id))
|
.select(schema::root_key_history::data_encryption_nonce)
|
||||||
.select(schema::root_key_history::data_encryption_nonce)
|
.first(&mut *conn)
|
||||||
.first(conn)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|()| {
|
let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|()| {
|
||||||
error!(
|
error!(
|
||||||
"Broken database: invalid nonce for root key history id={:#?}",
|
"Broken database: invalid nonce for root key history id={:#?}",
|
||||||
root_key_id
|
root_key_id
|
||||||
);
|
);
|
||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?;
|
})?;
|
||||||
nonce.increment();
|
nonce.increment();
|
||||||
|
|
||||||
update(schema::root_key_history::table)
|
update(schema::root_key_history::table)
|
||||||
.filter(schema::root_key_history::id.eq(root_key_id))
|
.filter(schema::root_key_history::id.eq(root_key_id))
|
||||||
.set(schema::root_key_history::data_encryption_nonce.eq(nonce.to_vec()))
|
.set(schema::root_key_history::data_encryption_nonce.eq(nonce.to_vec()))
|
||||||
.execute(conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Result::<_, Error>::Ok(nonce)
|
Result::<_, Error>::Ok(nonce)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -156,19 +180,28 @@ impl Vault {
|
|||||||
const fn expect_unsealed(state: &mut State) -> Result<&mut Unsealed, Error> {
|
const fn expect_unsealed(state: &mut State) -> Result<&mut Unsealed, Error> {
|
||||||
match state {
|
match state {
|
||||||
State::Unsealed(unsealed) => Ok(unsealed),
|
State::Unsealed(unsealed) => Ok(unsealed),
|
||||||
|
State::Bootstrapping { .. } => Err(Error::NotBootstrapped),
|
||||||
State::Unbootstrapped => Err(Error::NotBootstrapped),
|
State::Unbootstrapped => Err(Error::NotBootstrapped),
|
||||||
State::Sealed { .. } => Err(Error::Sealed),
|
State::Sealed { .. } => Err(Error::Sealed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
pub async fn finalize_bootstrap(&mut self) -> Result<(), Error> {
|
||||||
pub async fn bootstrap(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
|
let State::Bootstrapping {
|
||||||
if !matches!(self.state, State::Unbootstrapped) {
|
declared_operators,
|
||||||
|
current_passphrases,
|
||||||
|
} = &mut self.state
|
||||||
|
else {
|
||||||
return Err(Error::AlreadyBootstrapped);
|
return Err(Error::AlreadyBootstrapped);
|
||||||
}
|
};
|
||||||
let salt = v1::generate_salt();
|
|
||||||
let mut seal_key = derive_key(seal_key_raw, &salt);
|
|
||||||
let mut root_key = KeyCell::new_secure_random();
|
let mut root_key = KeyCell::new_secure_random();
|
||||||
|
let root_key_salt = v1::generate_salt();
|
||||||
|
|
||||||
|
let mut seal_key = KeyCell::new_secure_random();
|
||||||
|
|
||||||
|
let shares = seal_key.0.read_inline(|seal_key| {
|
||||||
|
generate_shamir_shares(current_passphrases.len() as u64, seal_key.as_slice())
|
||||||
|
});
|
||||||
|
|
||||||
// Zero nonces are fine because they are one-time
|
// Zero nonces are fine because they are one-time
|
||||||
let root_key_nonce = Nonce::default();
|
let root_key_nonce = Nonce::default();
|
||||||
@@ -184,33 +217,42 @@ impl Vault {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let data_encryption_nonce_bytes = data_encryption_nonce.to_vec();
|
||||||
let mut conn = self.db.get().await?;
|
let mut conn = self.db.get().await?;
|
||||||
|
|
||||||
let data_encryption_nonce_bytes = data_encryption_nonce.to_vec();
|
|
||||||
let root_key_history_id = conn
|
let root_key_history_id = conn
|
||||||
.transaction(|conn| {
|
.transaction(async |conn| {
|
||||||
Box::pin(async move {
|
for ((operator_id, raw_passphrase), raw_share) in
|
||||||
let root_key_history_id: RootKeyHistoryId =
|
current_passphrases.iter_mut().zip(shares.iter())
|
||||||
insert_into(schema::root_key_history::table)
|
{
|
||||||
.values(&models::NewRootKeyHistory {
|
let salt = v1::generate_salt();
|
||||||
ciphertext: root_key_ciphertext,
|
let mut share_seal_key = derive_key(&mut raw_passphrase, &salt);
|
||||||
tag: v1::ROOT_KEY_TAG.to_vec(),
|
let share_encryption_nonce = Nonce::default();
|
||||||
root_key_encryption_nonce: root_key_nonce.to_vec(),
|
|
||||||
data_encryption_nonce: data_encryption_nonce_bytes,
|
|
||||||
schema_version: 1,
|
|
||||||
salt: salt.to_vec(),
|
|
||||||
})
|
|
||||||
.returning(schema::root_key_history::id)
|
|
||||||
.get_result(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
update(schema::arbiter_settings::table)
|
let share_key = derive_key(&mut raw_passphrase, &salt);
|
||||||
.set(schema::arbiter_settings::root_key_id.eq(root_key_history_id))
|
}
|
||||||
.execute(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Result::<_, diesel::result::Error>::Ok(root_key_history_id)
|
let root_key_history_id = insert_into(schema::root_key_history::table)
|
||||||
})
|
.values(&models::NewRootKeyHistory {
|
||||||
|
ciphertext: root_key_ciphertext.clone(),
|
||||||
|
tag: v1::ROOT_KEY_TAG.to_vec(),
|
||||||
|
root_key_encryption_nonce: root_key_nonce.to_vec(),
|
||||||
|
data_encryption_nonce: data_encryption_nonce_bytes.clone(),
|
||||||
|
schema_version: 1,
|
||||||
|
salt: root_key_salt.to_vec(),
|
||||||
|
})
|
||||||
|
.returning(schema::root_key_history::id)
|
||||||
|
.get_result(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
update(schema::arbiter_settings::table)
|
||||||
|
.set(schema::arbiter_settings::root_key_id.eq(root_key_history_id))
|
||||||
|
.execute(&mut *conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Result::<_, diesel::result::Error>::Ok(RootKeyHistoryId::from_raw(
|
||||||
|
root_key_history_id,
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -224,11 +266,59 @@ impl Vault {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal / unseal / bootstrap stuff. Will be separated into another actor, eventually
|
||||||
|
#[messages]
|
||||||
|
impl Vault {
|
||||||
|
#[message]
|
||||||
|
pub async fn start_bootstrap(&mut self, declared_operators: u64) -> Result<(), Error> {
|
||||||
|
if !matches!(&self.state, State::Unbootstrapped) {
|
||||||
|
return Err(Error::AlreadyBootstrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = State::Bootstrapping {
|
||||||
|
declared_operators,
|
||||||
|
current_passphrases: HashMap::default(),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn try_unseal(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
|
pub async fn contribute_bootstrap(
|
||||||
|
&mut self,
|
||||||
|
operator: OperatorIdentityId,
|
||||||
|
key_raw: SafeCell<Vec<u8>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let State::Bootstrapping {
|
||||||
|
current_passphrases,
|
||||||
|
declared_operators,
|
||||||
|
} = &mut self.state
|
||||||
|
else {
|
||||||
|
return Err(Error::AlreadyBootstrapped);
|
||||||
|
};
|
||||||
|
|
||||||
|
if current_passphrases.contains_key(&operator) {
|
||||||
|
return Err(Error::AlreadyBootstrapped);
|
||||||
|
}
|
||||||
|
current_passphrases.insert(operator, key_raw);
|
||||||
|
|
||||||
|
if current_passphrases.len() == declared_operators {
|
||||||
|
return self.finalize_bootstrap(seal_key_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message]
|
||||||
|
pub async fn contribute_unseal(
|
||||||
|
&mut self,
|
||||||
|
operator: OperatorId,
|
||||||
|
key_raw: SafeCell<Vec<u8>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let State::Sealed {
|
let State::Sealed {
|
||||||
root_key_history_id,
|
root_key_history_id,
|
||||||
|
current_shares,
|
||||||
} = &self.state
|
} = &self.state
|
||||||
else {
|
else {
|
||||||
return Err(Error::NotBootstrapped);
|
return Err(Error::NotBootstrapped);
|
||||||
@@ -249,7 +339,7 @@ impl Vault {
|
|||||||
error!("Broken database: invalid salt for root key");
|
error!("Broken database: invalid salt for root key");
|
||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?;
|
})?;
|
||||||
let mut seal_key = derive_key(seal_key_raw, &salt);
|
let mut seal_key = derive_key(key_raw, &salt);
|
||||||
|
|
||||||
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
|
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
|
||||||
|
|
||||||
@@ -280,6 +370,25 @@ impl Vault {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message]
|
||||||
|
pub async fn seal(&mut self) -> Result<(), Error> {
|
||||||
|
let Unsealed {
|
||||||
|
root_key_history_id,
|
||||||
|
..
|
||||||
|
} = Self::expect_unsealed(&mut self.state)?;
|
||||||
|
|
||||||
|
self.state = State::Sealed {
|
||||||
|
root_key_history_id: *root_key_history_id,
|
||||||
|
current_shares: HashMap::new(),
|
||||||
|
};
|
||||||
|
let _ = self.events.tell(Publish(events::VaultResealed)).await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server-side cryptographic operations
|
||||||
|
#[messages]
|
||||||
|
impl Vault {
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
||||||
let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?;
|
let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?;
|
||||||
@@ -397,25 +506,47 @@ impl Vault {
|
|||||||
|
|
||||||
Ok(hmac.verify_slice(&expected_mac).is_ok())
|
Ok(hmac.verify_slice(&expected_mac).is_ok())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[message]
|
/// According to the spec, the quorum is 50% + 1
|
||||||
pub async fn seal(&mut self) -> Result<(), Error> {
|
/// with exception for 1 and 2 operators, those require exactly the number of operators registered
|
||||||
let Unsealed {
|
fn shamir_threshold(comittee_size: u64) -> u64 {
|
||||||
root_key_history_id,
|
if comittee_size == 2 || comittee_size == 1 {
|
||||||
..
|
return comittee_size;
|
||||||
} = Self::expect_unsealed(&mut self.state)?;
|
|
||||||
|
|
||||||
self.state = State::Sealed {
|
|
||||||
root_key_history_id: *root_key_history_id,
|
|
||||||
};
|
|
||||||
let _ = self.events.tell(Publish(events::VaultResealed)).await;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let half_comittee = match comittee_size % 2 != 0 {
|
||||||
|
true => (comittee_size - 1) / 2,
|
||||||
|
false => comittee_size / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
half_comittee + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Beware: this function accepts raw key references (without memory protection)
|
||||||
|
fn generate_shamir_shares(threshold: u64, key: &[u8]) -> Vec<SafeCell<Vec<u8>>> {
|
||||||
|
use vsss_rs::{shamir, *};
|
||||||
|
|
||||||
|
type P256Share = DefaultShare<IdentifierPrimeField<Scalar>, IdentifierPrimeField<Scalar>>;
|
||||||
|
|
||||||
|
let mut osrng = rand_core::OsRng::default();
|
||||||
|
let sk = SecretKey::random(&mut osrng);
|
||||||
|
let nzs = sk.to_nonzero_scalar();
|
||||||
|
let shared_secret = IdentifierPrimeField(*nzs.as_ref());
|
||||||
|
let res = shamir::split_secret::<P256Share>(2, 3, &shared_secret, &mut osrng);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let shares = res.unwrap();
|
||||||
|
let res = shares.combine();
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let scalar = res.unwrap();
|
||||||
|
let nzs_dup = NonZeroScalar::from_repr(scalar.0.to_repr()).unwrap();
|
||||||
|
let sk_dup = SecretKey::from(nzs_dup);
|
||||||
|
assert_eq!(sk_dup.to_bytes(), sk.to_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{actors::GlobalActors, db::models::RootKeyHistory};
|
use crate::actors::GlobalActors;
|
||||||
use arbiter_crypto::safecell::SafeCellHandle as _;
|
use arbiter_crypto::safecell::SafeCellHandle as _;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -425,7 +556,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
let seal_key = SafeCell::new(b"test-seal-key".to_vec());
|
||||||
actor.bootstrap(seal_key).await.unwrap();
|
actor.finalize_bootstrap(seal_key).await.unwrap();
|
||||||
actor
|
actor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,28 +174,26 @@ impl TlsManager {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut conn = db.get().await?;
|
let mut conn = db.get().await?;
|
||||||
conn.transaction(|conn| {
|
conn.transaction(async |conn| {
|
||||||
Box::pin(async {
|
let new_tls_history = NewTlsHistory {
|
||||||
let new_tls_history = NewTlsHistory {
|
cert: new_cert.cert.pem(),
|
||||||
cert: new_cert.cert.pem(),
|
cert_key: new_cert.cert_key.serialize_pem(),
|
||||||
cert_key: new_cert.cert_key.serialize_pem(),
|
ca_cert: encode_cert_to_pem(&ca.cert),
|
||||||
ca_cert: encode_cert_to_pem(&ca.cert),
|
ca_key: ca.issuer.key().serialize_pem(),
|
||||||
ca_key: ca.issuer.key().serialize_pem(),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let inserted_tls_history: i32 = diesel::insert_into(tls_history::table)
|
let inserted_tls_history: i32 = diesel::insert_into(tls_history::table)
|
||||||
.values(&new_tls_history)
|
.values(&new_tls_history)
|
||||||
.returning(tls_history::id)
|
.returning(tls_history::id)
|
||||||
.get_result(conn)
|
.get_result(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
diesel::update(arbiter_settings::table)
|
diesel::update(arbiter_settings::table)
|
||||||
.set(arbiter_settings::tls_id.eq(inserted_tls_history))
|
.set(arbiter_settings::tls_id.eq(inserted_tls_history))
|
||||||
.execute(conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Result::<_, diesel::result::Error>::Ok(())
|
Result::<_, diesel::result::Error>::Ok(())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl TryFrom<SafeCell<Vec<u8>>> for KeyCell {
|
|||||||
if value.len() != size_of::<Key>() {
|
if value.len() != size_of::<Key>() {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
let cell = SafeCell::new_inline(|cell_write: &mut Key| {
|
let cell = SafeCell::new_inline_default(|cell_write: &mut Key| {
|
||||||
cell_write.copy_from_slice(&value);
|
cell_write.copy_from_slice(&value);
|
||||||
});
|
});
|
||||||
Ok(Self(cell))
|
Ok(Self(cell))
|
||||||
@@ -37,7 +37,7 @@ impl TryFrom<SafeCell<Vec<u8>>> for KeyCell {
|
|||||||
|
|
||||||
impl KeyCell {
|
impl KeyCell {
|
||||||
pub fn new_secure_random() -> Self {
|
pub fn new_secure_random() -> Self {
|
||||||
let key = SafeCell::new_inline(|key_buffer: &mut Key| {
|
let key = SafeCell::new_inline_default(|key_buffer: &mut Key| {
|
||||||
let mut rng = StdRng::try_from_rng(&mut SysRng)
|
let mut rng = StdRng::try_from_rng(&mut SysRng)
|
||||||
.expect("Rng failure is unrecoverable and should panic");
|
.expect("Rng failure is unrecoverable and should panic");
|
||||||
rng.fill_bytes(key_buffer);
|
rng.fill_bytes(key_buffer);
|
||||||
@@ -94,7 +94,7 @@ impl KeyCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
||||||
pub fn derive_key(mut password: SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
pub fn derive_key(password: &mut SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
||||||
let params = {
|
let params = {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -271,8 +271,8 @@ pub struct ProgramClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
#[derive(Queryable, Debug)]
|
||||||
#[diesel(table_name = schema::operator_identity, check_for_backend(Sqlite))]
|
#[diesel(table_name = schema::operator_client, check_for_backend(Sqlite))]
|
||||||
pub struct OperatorIdentity {
|
pub struct OperatorClient {
|
||||||
pub id: OperatorIdentityId,
|
pub id: OperatorIdentityId,
|
||||||
pub public_key: Vec<u8>,
|
pub public_key: Vec<u8>,
|
||||||
pub created_at: SqliteTimestamp,
|
pub created_at: SqliteTimestamp,
|
||||||
|
|||||||
@@ -179,24 +179,22 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if run_kind == RunKind::Execution {
|
if run_kind == RunKind::Execution {
|
||||||
conn.transaction(|conn| {
|
conn.transaction(async |conn| {
|
||||||
Box::pin(async move {
|
let log_id: i32 = insert_into(evm_transaction_log::table)
|
||||||
let log_id: i32 = insert_into(evm_transaction_log::table)
|
.values(&NewEvmTransactionLog {
|
||||||
.values(&NewEvmTransactionLog {
|
grant_id: grant.common_settings_id,
|
||||||
grant_id: grant.common_settings_id,
|
wallet_access_id: context.target.id,
|
||||||
wallet_access_id: context.target.id,
|
chain_id: context.chain.into(),
|
||||||
chain_id: context.chain.into(),
|
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
||||||
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
signed_at: Utc::now().into(),
|
||||||
signed_at: Utc::now().into(),
|
})
|
||||||
})
|
.returning(evm_transaction_log::id)
|
||||||
.returning(evm_transaction_log::id)
|
.get_result(&mut *conn)
|
||||||
.get_result(conn)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
P::record_transaction(&context, meaning, log_id, &grant, conn).await?;
|
P::record_transaction(&context, meaning, log_id, &grant, &mut *conn).await?;
|
||||||
|
|
||||||
QueryResult::Ok(())
|
QueryResult::Ok(())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(DatabaseError::from)?;
|
.map_err(DatabaseError::from)?;
|
||||||
@@ -222,54 +220,52 @@ impl Engine {
|
|||||||
let vault = self.vault.clone();
|
let vault = self.vault.clone();
|
||||||
|
|
||||||
let id = conn
|
let id = conn
|
||||||
.transaction(|conn| {
|
.transaction(async |conn| {
|
||||||
Box::pin(async move {
|
use schema::evm_basic_grant;
|
||||||
use schema::evm_basic_grant;
|
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::cast_possible_truncation,
|
clippy::cast_possible_truncation,
|
||||||
clippy::cast_possible_wrap,
|
clippy::cast_possible_wrap,
|
||||||
clippy::as_conversions,
|
clippy::as_conversions,
|
||||||
reason = "fixme! #86"
|
reason = "fixme! #86"
|
||||||
)]
|
)]
|
||||||
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
||||||
.values(&NewEvmBasicGrant {
|
.values(&NewEvmBasicGrant {
|
||||||
chain_id: full_grant.shared.chain.into(),
|
chain_id: full_grant.shared.chain.into(),
|
||||||
wallet_access_id: full_grant.shared.wallet_access_id,
|
wallet_access_id: full_grant.shared.wallet_access_id,
|
||||||
valid_from: full_grant.shared.valid_from.map(SqliteTimestamp),
|
valid_from: full_grant.shared.valid_from.map(SqliteTimestamp),
|
||||||
valid_until: full_grant.shared.valid_until.map(SqliteTimestamp),
|
valid_until: full_grant.shared.valid_until.map(SqliteTimestamp),
|
||||||
max_gas_fee_per_gas: full_grant
|
max_gas_fee_per_gas: full_grant
|
||||||
.shared
|
.shared
|
||||||
.max_gas_fee_per_gas
|
.max_gas_fee_per_gas
|
||||||
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
||||||
max_priority_fee_per_gas: full_grant
|
max_priority_fee_per_gas: full_grant
|
||||||
.shared
|
.shared
|
||||||
.max_priority_fee_per_gas
|
.max_priority_fee_per_gas
|
||||||
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
||||||
rate_limit_count: full_grant
|
rate_limit_count: full_grant
|
||||||
.shared
|
.shared
|
||||||
.rate_limit
|
.rate_limit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|rl| rl.count as i32),
|
.map(|rl| rl.count as i32),
|
||||||
rate_limit_window_secs: full_grant
|
rate_limit_window_secs: full_grant
|
||||||
.shared
|
.shared
|
||||||
.rate_limit
|
.rate_limit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|rl| rl.window.num_seconds() as i32),
|
.map(|rl| rl.window.num_seconds() as i32),
|
||||||
revoked_at: None,
|
revoked_at: None,
|
||||||
})
|
})
|
||||||
.returning(evm_basic_grant::all_columns)
|
.returning(evm_basic_grant::all_columns)
|
||||||
.get_result(conn)
|
.get_result(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
P::create_grant(&basic_grant, &full_grant.specific, conn).await?;
|
P::create_grant(&basic_grant, &full_grant.specific, &mut *conn).await?;
|
||||||
|
|
||||||
integrity::sign_entity(conn, &vault, &full_grant, basic_grant.id)
|
integrity::sign_entity(&mut *conn, &vault, &full_grant, basic_grant.id)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| diesel::result::Error::RollbackTransaction)?;
|
.map_err(|_| diesel::result::Error::RollbackTransaction)?;
|
||||||
|
|
||||||
QueryResult::Ok(basic_grant.id)
|
QueryResult::Ok(basic_grant.id)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl std::fmt::Debug for SafeSigner {
|
|||||||
/// Returns the protected key bytes and the derived Ethereum address.
|
/// Returns the protected key bytes and the derived Ethereum address.
|
||||||
pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) {
|
pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) {
|
||||||
loop {
|
loop {
|
||||||
let mut cell = SafeCell::new_inline(|w: &mut [u8; 32]| {
|
let mut cell = SafeCell::new_inline_default(|w: &mut [u8; 32]| {
|
||||||
rng.fill_bytes(w);
|
rng.fill_bytes(w);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub(super) async fn dispatch(
|
|||||||
VaultRequestPayload::QueryState(()) => {
|
VaultRequestPayload::QueryState(()) => {
|
||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
|
Ok(VaultState::Bootstrapping) => ProtoVaultState::Boostrapping,
|
||||||
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
||||||
Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
|
Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
|
||||||
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
|
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db::models::{EvmWalletAccess, EvmWalletId},
|
db::models::EvmWalletAccess,
|
||||||
evm::policies::{SharedGrantSettings, SpecificGrant, TransactionRateLimit, VolumeRateLimit},
|
evm::policies::{SharedGrantSettings, SpecificGrant, TransactionRateLimit, VolumeRateLimit},
|
||||||
grpc::Convert,
|
grpc::Convert,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db::models::{ClientId, NewEvmWalletAccess},
|
db::models::NewEvmWalletAccess,
|
||||||
grpc::Convert,
|
grpc::Convert,
|
||||||
peers::operator::{
|
peers::operator::{
|
||||||
OperatorSession, OutOfBand,
|
OperatorSession, OutOfBand,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::{
|
|||||||
peers::operator::{OperatorSession, session::handlers::HandleQueryVaultState},
|
peers::operator::{OperatorSession, session::handlers::HandleQueryVaultState},
|
||||||
};
|
};
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::shared::VaultState as ProtoVaultState,
|
|
||||||
proto::operator::{
|
proto::operator::{
|
||||||
operator_response::Payload as OperatorResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
vault::{
|
vault::{
|
||||||
@@ -11,6 +10,7 @@ use arbiter_proto::{
|
|||||||
response::Payload as VaultResponsePayload,
|
response::Payload as VaultResponsePayload,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
proto::shared::VaultState as ProtoVaultState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use kameo::actor::ActorRef;
|
use kameo::actor::ActorRef;
|
||||||
@@ -47,6 +47,7 @@ async fn handle_query_vault_state(
|
|||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
||||||
|
Ok(VaultState::Bootstrapping) => ProtoVaultState::Boostrapping,
|
||||||
Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
|
Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(error = ?err, "Failed to query vault state");
|
warn!(error = ?err, "Failed to query vault state");
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::{
|
|||||||
peers::operator::vault_gate::{self as vault_gate},
|
peers::operator::vault_gate::{self as vault_gate},
|
||||||
};
|
};
|
||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
shared::VaultState as ProtoVaultState,
|
|
||||||
operator::{
|
operator::{
|
||||||
operator_response::Payload as OperatorResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
vault::{
|
vault::{
|
||||||
@@ -17,6 +16,7 @@ use arbiter_proto::proto::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shared::VaultState as ProtoVaultState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
@@ -46,6 +46,7 @@ impl Convert for VaultState {
|
|||||||
fn convert(self) -> OperatorResponsePayload {
|
fn convert(self) -> OperatorResponsePayload {
|
||||||
let proto_state = match self {
|
let proto_state = match self {
|
||||||
Self::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
Self::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
||||||
|
Self::Bootstrapping => ProtoVaultState::Boostrapping,
|
||||||
Self::Sealed => ProtoVaultState::Sealed,
|
Self::Sealed => ProtoVaultState::Sealed,
|
||||||
Self::Unsealed => ProtoVaultState::Unsealed,
|
Self::Unsealed => ProtoVaultState::Unsealed,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -171,46 +171,42 @@ async fn insert_client(
|
|||||||
Error::DatabasePoolUnavailable
|
Error::DatabasePoolUnavailable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
conn.exclusive_transaction(|conn| {
|
conn.exclusive_transaction(async |conn| {
|
||||||
let vault = vault.clone();
|
let metadata_id = insert_into(client_metadata::table)
|
||||||
let pubkey = pubkey.clone();
|
.values((
|
||||||
Box::pin(async move {
|
client_metadata::name.eq(&metadata.name),
|
||||||
let metadata_id = insert_into(client_metadata::table)
|
client_metadata::description.eq(&metadata.description),
|
||||||
.values((
|
client_metadata::version.eq(&metadata.version),
|
||||||
client_metadata::name.eq(&metadata.name),
|
))
|
||||||
client_metadata::description.eq(&metadata.description),
|
.returning(client_metadata::id)
|
||||||
client_metadata::version.eq(&metadata.version),
|
.get_result::<i32>(&mut *conn)
|
||||||
))
|
.await?;
|
||||||
.returning(client_metadata::id)
|
|
||||||
.get_result::<i32>(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let client_id = insert_into(program_client::table)
|
let client_id = insert_into(program_client::table)
|
||||||
.values((
|
.values((
|
||||||
program_client::public_key.eq(pubkey.to_bytes()),
|
program_client::public_key.eq(pubkey.to_bytes()),
|
||||||
program_client::metadata_id.eq(metadata_id),
|
program_client::metadata_id.eq(metadata_id),
|
||||||
))
|
))
|
||||||
.on_conflict_do_nothing()
|
.on_conflict_do_nothing()
|
||||||
.returning(program_client::id)
|
.returning(program_client::id)
|
||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
integrity::sign_entity(
|
integrity::sign_entity(
|
||||||
conn,
|
&mut *conn,
|
||||||
&vault,
|
vault,
|
||||||
&ClientCredentials {
|
&ClientCredentials {
|
||||||
pubkey: pubkey.clone(),
|
pubkey: pubkey.clone(),
|
||||||
},
|
},
|
||||||
client_id,
|
client_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = ?e, "Failed to sign integrity tag for new client key");
|
error!(error = ?e, "Failed to sign integrity tag for new client key");
|
||||||
Error::DatabaseOperationFailed
|
Error::DatabaseOperationFailed
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(client_id)
|
Ok(client_id)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -229,55 +225,51 @@ async fn sync_client_metadata(
|
|||||||
Error::DatabasePoolUnavailable
|
Error::DatabasePoolUnavailable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
conn.exclusive_transaction(|conn| {
|
conn.exclusive_transaction(async |conn| {
|
||||||
let metadata = metadata.clone();
|
let (current_metadata_id, current): (i32, ProgramClientMetadata) = program_client::table
|
||||||
Box::pin(async move {
|
.find(client_id)
|
||||||
let (current_metadata_id, current): (i32, ProgramClientMetadata) =
|
.inner_join(client_metadata::table)
|
||||||
program_client::table
|
.select((
|
||||||
.find(client_id)
|
program_client::metadata_id,
|
||||||
.inner_join(client_metadata::table)
|
ProgramClientMetadata::as_select(),
|
||||||
.select((
|
))
|
||||||
program_client::metadata_id,
|
.first(&mut *conn)
|
||||||
ProgramClientMetadata::as_select(),
|
.await?;
|
||||||
))
|
|
||||||
.first(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let unchanged = current.name == metadata.name
|
let unchanged = current.name == metadata.name
|
||||||
&& current.description == metadata.description
|
&& current.description == metadata.description
|
||||||
&& current.version == metadata.version;
|
&& current.version == metadata.version;
|
||||||
if unchanged {
|
if unchanged {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_into(client_metadata_history::table)
|
insert_into(client_metadata_history::table)
|
||||||
.values((
|
.values((
|
||||||
client_metadata_history::metadata_id.eq(current_metadata_id),
|
client_metadata_history::metadata_id.eq(current_metadata_id),
|
||||||
client_metadata_history::client_id.eq(client_id),
|
client_metadata_history::client_id.eq(client_id),
|
||||||
))
|
))
|
||||||
.execute(conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let metadata_id = insert_into(client_metadata::table)
|
let metadata_id = insert_into(client_metadata::table)
|
||||||
.values((
|
.values((
|
||||||
client_metadata::name.eq(&metadata.name),
|
client_metadata::name.eq(&metadata.name),
|
||||||
client_metadata::description.eq(&metadata.description),
|
client_metadata::description.eq(&metadata.description),
|
||||||
client_metadata::version.eq(&metadata.version),
|
client_metadata::version.eq(&metadata.version),
|
||||||
))
|
))
|
||||||
.returning(client_metadata::id)
|
.returning(client_metadata::id)
|
||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
update(program_client::table.find(client_id))
|
update(program_client::table.find(client_id))
|
||||||
.set((
|
.set((
|
||||||
program_client::metadata_id.eq(metadata_id),
|
program_client::metadata_id.eq(metadata_id),
|
||||||
program_client::updated_at.eq(now),
|
program_client::updated_at.eq(now),
|
||||||
))
|
))
|
||||||
.execute(conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok::<(), diesel::result::Error>(())
|
Ok::<(), diesel::result::Error>(())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -175,20 +175,18 @@ impl OperatorSession {
|
|||||||
entries: Vec<NewEvmWalletAccess>,
|
entries: Vec<NewEvmWalletAccess>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut conn = self.props.db.get().await?;
|
let mut conn = self.props.db.get().await?;
|
||||||
conn.transaction(|conn| {
|
conn.transaction(async |conn| {
|
||||||
Box::pin(async move {
|
use crate::db::schema::evm_wallet_access;
|
||||||
use crate::db::schema::evm_wallet_access;
|
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
diesel::insert_into(evm_wallet_access::table)
|
diesel::insert_into(evm_wallet_access::table)
|
||||||
.values(&entry)
|
.values(&entry)
|
||||||
.on_conflict_do_nothing()
|
.on_conflict_do_nothing()
|
||||||
.execute(conn)
|
.execute(&mut *conn)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result::<_, Error>::Ok(())
|
Result::<_, Error>::Ok(())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -200,18 +198,16 @@ impl OperatorSession {
|
|||||||
entries: Vec<i32>,
|
entries: Vec<i32>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut conn = self.props.db.get().await?;
|
let mut conn = self.props.db.get().await?;
|
||||||
conn.transaction(|conn| {
|
conn.transaction(async |conn| {
|
||||||
Box::pin(async move {
|
use crate::db::schema::evm_wallet_access;
|
||||||
use crate::db::schema::evm_wallet_access;
|
for entry in entries {
|
||||||
for entry in entries {
|
diesel::delete(evm_wallet_access::table)
|
||||||
diesel::delete(evm_wallet_access::table)
|
.filter(evm_wallet_access::wallet_id.eq(entry))
|
||||||
.filter(evm_wallet_access::wallet_id.eq(entry))
|
.execute(&mut *conn)
|
||||||
.execute(conn)
|
.await?;
|
||||||
.await?;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Result::<_, Error>::Ok(())
|
Result::<_, Error>::Ok(())
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user