diff --git a/mise.toml b/mise.toml index f0da5e6..a1f3729 100644 --- a/mise.toml +++ b/mise.toml @@ -22,3 +22,5 @@ run = ''' dart pub global activate protoc_plugin && \ protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ $(find protobufs -name '*.proto' | sort) ''' + +[tasks.generate_schema] diff --git a/protobufs/shared/vault.proto b/protobufs/shared/vault.proto index 795539f..9e5c2d3 100644 --- a/protobufs/shared/vault.proto +++ b/protobufs/shared/vault.proto @@ -5,7 +5,8 @@ package arbiter.shared; enum VaultState { VAULT_STATE_UNSPECIFIED = 0; VAULT_STATE_UNBOOTSTRAPPED = 1; - VAULT_STATE_SEALED = 2; - VAULT_STATE_UNSEALED = 3; - VAULT_STATE_ERROR = 4; + VAULT_STATE_BOOSTRAPPING = 2; + VAULT_STATE_SEALED = 3; + VAULT_STATE_UNSEALED = 4; + VAULT_STATE_ERROR = 5; } diff --git a/server/Cargo.lock b/server/Cargo.lock index e36c264..ba262e3 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common 0.1.7", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -786,6 +786,7 @@ dependencies = [ "tonic", "tracing", "tracing-subscriber", + "vsss-rs", "x25519-dalek 2.0.1", ] @@ -1283,7 +1284,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1612,8 +1613,22 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", + "serdect 0.2.0", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130" +dependencies = [ + "num-traits", + "rand_core 0.6.4", + "serdect 0.3.0", "subtle", "zeroize", ] @@ -1624,7 +1639,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] @@ -1927,7 +1942,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2007,7 +2022,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve", "rfc6979", - "serdect", + "serdect 0.2.0", "signature 2.2.0", "spki 0.7.3", ] @@ -2040,16 +2055,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", - "crypto-bigint", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", + "hkdf", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", - "serdect", + "serdect 0.2.0", "subtle", + "tap", + "zeroize", +] + +[[package]] +name = "elliptic-curve-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de2b6fae800f08032a6ea32995b52925b1d451bff9d445c8ab2932323277faf" +dependencies = [ + "elliptic-curve", + "heapless", + "hex", + "multiexp", + "serde", "zeroize", ] @@ -2123,6 +2154,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -2323,6 +2355,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "generic-array" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +dependencies = [ + "rustversion", + "serde_core", + "typenum", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -2406,6 +2449,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2446,6 +2498,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -2473,6 +2535,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2543,6 +2614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" dependencies = [ "ctutils", + "serde", "typenum", "zeroize", ] @@ -2808,7 +2880,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2947,7 +3019,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "serdect", + "serdect 0.2.0", "sha2 0.10.9", "signature 2.2.0", ] @@ -3290,6 +3362,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "multiexp" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec2ce93a6f06ac6cae04c1da3f2a6a24fcfc1f0eb0b4e0f3d302f0df45326cb" +dependencies = [ + "ff", + "group", + "rand_core 0.6.4", + "rustversion", + "std-shims", + "zeroize", +] + [[package]] name = "multimap" version = "0.10.1" @@ -3321,6 +3407,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3329,6 +3429,19 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "rand 0.8.6", + "serde", ] [[package]] @@ -3346,6 +3459,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -4454,9 +4590,9 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der 0.7.10", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", - "serdect", + "serdect 0.2.0", "subtle", "zeroize", ] @@ -4622,6 +4758,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -4787,6 +4933,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "spki" version = "0.7.3" @@ -4831,6 +4983,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "std-shims" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227c4f8561598188d0df96dbe749824576174bba278b5b6bb2eacff1066067d0" +dependencies = [ + "hashbrown 0.16.1", + "rustversion", + "spin", +] + [[package]] name = "string_morph" version = "0.1.0" @@ -5574,6 +5737,27 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsss-rs" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ec751bdcc8bda099e269b24cc6b4ad14f9ce8b0490c1599174070e792ecd70c" +dependencies = [ + "crypto-bigint 0.5.5", + "crypto-bigint 0.6.1", + "elliptic-curve", + "elliptic-curve-tools", + "generic-array 1.4.1", + "hex", + "hybrid-array", + "num", + "rand_core 0.6.4", + "serde", + "sha3 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "wait-timeout" version = "0.2.1" diff --git a/server/crates/arbiter-crypto/src/safecell.rs b/server/crates/arbiter-crypto/src/safecell.rs index 15b0044..b16194c 100644 --- a/server/crates/arbiter-crypto/src/safecell.rs +++ b/server/crates/arbiter-crypto/src/safecell.rs @@ -22,7 +22,7 @@ pub trait SafeCellHandle { fn read(&mut self) -> Self::CellRead<'_>; fn write(&mut self) -> Self::CellWrite<'_>; - fn new_inline(f: F) -> Self + fn new_inline_default(f: F) -> Self where Self: Sized, T: Default, @@ -36,6 +36,14 @@ pub trait SafeCellHandle { cell } + fn new_inline(f: Box) -> Self + where + Self: Sized, + F: for<'a> FnOnce() -> T, + { + Self::new(f()) + } + #[inline(always)] fn read_inline(&mut self, f: F) -> R where diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index 7790bd6..bc7cb3f 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -50,6 +50,7 @@ subtle = "2.6.1" x25519-dalek.workspace = true k256.workspace = true kameo_actors.workspace = true +vsss-rs = "5.4.0" [dev-dependencies] proptest = "1.11.0" diff --git a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql index 0ab9a3f..37509f1 100644 --- a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql +++ b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql @@ -43,13 +43,24 @@ create table if not exists arbiter_settings ( insert into arbiter_settings (id) values (1) on conflict do nothing; -- ensure singleton row exists -create table if not exists operator_client ( +create table if not exists operator_identity ( id integer not null primary key, public_key blob not null, created_at integer not null default(unixepoch ('now')), updated_at integer not null default(unixepoch ('now')) ) STRICT; -create unique index if not exists uniq_operator_client_public_key on operator_client (public_key); +create unique index if not exists uniq_operator_identity_public_key on operator_identity (public_key); + +create table if not exists operator ( + id integer primary key references operator_identity(id) on delete restrict, -- same id as operator_identity + + share blob not null, + share_nonce blob not null, + + created_at integer not null default(unixepoch ('now')), + updated_at integer not null default(unixepoch ('now')) + +) STRICT; create table if not exists client_metadata ( id integer not null primary key, diff --git a/server/crates/arbiter-server/src/actors/bootstrap.rs b/server/crates/arbiter-server/src/actors/bootstrap.rs index 8ec0059..c1fe6af 100644 --- a/server/crates/arbiter-server/src/actors/bootstrap.rs +++ b/server/crates/arbiter-server/src/actors/bootstrap.rs @@ -48,7 +48,7 @@ impl Bootstrapper { let row_count: i64 = { let mut conn = db.get().await?; - schema::operator_client::table + schema::operator::table .count() .get_result(&mut conn) .await? diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index 11392ec..033d11f 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -3,7 +3,7 @@ use crate::{ crypto::integrity, db::{ DatabaseError, DatabasePool, - models::{self}, + models::{self, EvmWalletId}, schema, }, evm::{ @@ -116,7 +116,7 @@ impl EvmActor { } #[message] - pub async fn list_wallets(&self) -> Result, Error> { + pub async fn list_wallets(&self) -> Result, Error> { let mut conn = self.db.get().await.map_err(DatabaseError::from)?; let rows: Vec = schema::evm_wallet::table .select(models::EvmWallet::as_select()) diff --git a/server/crates/arbiter-server/src/actors/vault/mod.rs b/server/crates/arbiter-server/src/actors/vault/mod.rs index cea0690..a17ef63 100644 --- a/server/crates/arbiter-server/src/actors/vault/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::{ crypto::{ KeyCell, derive_key, @@ -6,7 +8,7 @@ use crate::{ }, db::{ self, - models::{self, RootKeyHistory}, + models::{self, OperatorId, OperatorIdentityId, RootKeyHistory, RootKeyHistoryId}, schema::{self}, }, }; @@ -15,17 +17,17 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use chrono::Utc; use diesel::{ ExpressionMethods as _, OptionalExtension, QueryDsl, SelectableHelper, - dsl::{insert_into, update}, + dsl::{count, insert_into, update}, + select, }; 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_actors::message_bus::{MessageBus, Publish}; use strum::{EnumDiscriminants, IntoDiscriminant}; use tracing::{error, info}; pub mod events { - #[derive(Clone, Copy)] pub struct Bootstrapped; @@ -63,8 +65,17 @@ pub enum Error { 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 { - root_key_history_id: i32, + root_key_history_id: RootKeyHistoryId, root_key: KeyCell, } @@ -73,8 +84,16 @@ struct Unsealed { enum State { #[default] Unbootstrapped, + + Bootstrapping { + declared_operators: u64, + current_passphrases: HashMap>>, + }, + Sealed { - root_key_history_id: i32, + threshold: u64, // basically, quorum size + root_key_history_id: RootKeyHistoryId, + current_shares: HashMap>>, }, Unsealed(Unsealed), } @@ -90,7 +109,6 @@ pub struct Vault { events: ActorRef, } -#[messages] impl Vault { pub async fn new(db: db::DatabasePool, events: ActorRef) -> Result { let state = { @@ -103,9 +121,17 @@ impl Vault { .await?; match root_key_history { - Some(root_key_history) => State::Sealed { - root_key_history_id: root_key_history.id, - }, + Some(root_key_history) => { + 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, } }; @@ -115,7 +141,10 @@ impl Vault { // Exclusive transaction to avoid race condtions if multiple vaults write // additional layer of protection against nonce-reuse - async fn get_new_nonce(pool: &db::DatabasePool, root_key_id: i32) -> Result { + async fn get_new_nonce( + pool: &db::DatabasePool, + root_key_id: RootKeyHistoryId, + ) -> Result { let mut conn = pool.get().await?; let nonce = conn @@ -128,7 +157,7 @@ impl Vault { let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|()| { error!( - "Broken database: invalid nonce for root key history id={}", + "Broken database: invalid nonce for root key history id={:#?}", root_key_id ); Error::BrokenDatabase @@ -151,19 +180,28 @@ impl Vault { const fn expect_unsealed(state: &mut State) -> Result<&mut Unsealed, Error> { match state { State::Unsealed(unsealed) => Ok(unsealed), + State::Bootstrapping { .. } => Err(Error::NotBootstrapped), State::Unbootstrapped => Err(Error::NotBootstrapped), State::Sealed { .. } => Err(Error::Sealed), } } - #[message] - pub async fn bootstrap(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { - if !matches!(self.state, State::Unbootstrapped) { + pub async fn finalize_bootstrap(&mut self) -> Result<(), Error> { + let State::Bootstrapping { + declared_operators, + current_passphrases, + } = &mut self.state + else { 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 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 let root_key_nonce = Nonce::default(); @@ -179,19 +217,29 @@ impl Vault { }) })?; + let data_encryption_nonce_bytes = data_encryption_nonce.to_vec(); let mut conn = self.db.get().await?; - let data_encryption_nonce_bytes = data_encryption_nonce.to_vec(); let root_key_history_id = conn .transaction(async |conn| { - let root_key_history_id: i32 = insert_into(schema::root_key_history::table) + for ((operator_id, raw_passphrase), raw_share) in + current_passphrases.iter_mut().zip(shares.iter()) + { + let salt = v1::generate_salt(); + let mut share_seal_key = derive_key(&mut raw_passphrase, &salt); + let share_encryption_nonce = Nonce::default(); + + let share_key = derive_key(&mut raw_passphrase, &salt); + } + + 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: salt.to_vec(), + salt: root_key_salt.to_vec(), }) .returning(schema::root_key_history::id) .get_result(&mut *conn) @@ -202,7 +250,9 @@ impl Vault { .execute(&mut *conn) .await?; - Result::<_, diesel::result::Error>::Ok(root_key_history_id) + Result::<_, diesel::result::Error>::Ok(RootKeyHistoryId::from_raw( + root_key_history_id, + )) }) .await?; @@ -216,11 +266,59 @@ impl Vault { 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] - pub async fn try_unseal(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { + pub async fn contribute_bootstrap( + &mut self, + operator: OperatorIdentityId, + key_raw: SafeCell>, + ) -> 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>, + ) -> Result<(), Error> { let State::Sealed { root_key_history_id, + current_shares, } = &self.state else { return Err(Error::NotBootstrapped); @@ -241,7 +339,7 @@ impl Vault { error!("Broken database: invalid salt for root key"); 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()); @@ -272,6 +370,25 @@ impl Vault { 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] pub async fn decrypt(&mut self, aead_id: i32) -> Result>, Error> { let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?; @@ -340,7 +457,10 @@ impl Vault { } #[message] - pub fn sign_integrity(&mut self, mac_input: Vec) -> Result<(i32, Vec), Error> { + pub fn sign_integrity( + &mut self, + mac_input: Vec, + ) -> Result<(RootKeyHistoryId, Vec), Error> { let Unsealed { root_key, root_key_history_id, @@ -352,7 +472,7 @@ impl Vault { Ok(v) => v, Err(_) => unreachable!("HMAC accepts keys of any size"), }); - hmac.update(&root_key_history_id.to_be_bytes()); + hmac.update(&root_key_history_id.to_raw().to_be_bytes()); hmac.update(&mac_input); let mac = hmac.finalize().into_bytes().to_vec(); @@ -364,7 +484,7 @@ impl Vault { &mut self, mac_input: Vec, expected_mac: Vec, - key_version: i32, + key_version: RootKeyHistoryId, ) -> Result { let Unsealed { root_key, @@ -381,25 +501,47 @@ impl Vault { Ok(v) => v, Err(_) => unreachable!("HMAC accepts keys of any size"), }); - hmac.update(&key_version.to_be_bytes()); + hmac.update(&key_version.to_raw().to_be_bytes()); hmac.update(&mac_input); Ok(hmac.verify_slice(&expected_mac).is_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, - }; - let _ = self.events.tell(Publish(events::VaultResealed)).await; - Ok(()) +/// According to the spec, the quorum is 50% + 1 +/// with exception for 1 and 2 operators, those require exactly the number of operators registered +fn shamir_threshold(comittee_size: u64) -> u64 { + if comittee_size == 2 || comittee_size == 1 { + return comittee_size; } + + 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>> { + use vsss_rs::{shamir, *}; + + type P256Share = DefaultShare, IdentifierPrimeField>; + + 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::(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)] @@ -414,7 +556,7 @@ mod tests { .await .unwrap(); 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 } @@ -440,8 +582,8 @@ mod tests { assert!(n2.to_vec() > n1.to_vec(), "nonce must increase"); let mut conn = db.get().await.unwrap(); - let root_row: models::RootKeyHistory = schema::root_key_history::table - .select(models::RootKeyHistory::as_select()) + let root_row: RootKeyHistory = schema::root_key_history::table + .select(RootKeyHistory::as_select()) .first(&mut conn) .await .unwrap(); diff --git a/server/crates/arbiter-server/src/crypto/mod.rs b/server/crates/arbiter-server/src/crypto/mod.rs index 440cb24..13f2485 100644 --- a/server/crates/arbiter-server/src/crypto/mod.rs +++ b/server/crates/arbiter-server/src/crypto/mod.rs @@ -28,7 +28,7 @@ impl TryFrom>> for KeyCell { if value.len() != size_of::() { 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); }); Ok(Self(cell)) @@ -37,7 +37,7 @@ impl TryFrom>> for KeyCell { impl KeyCell { 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) .expect("Rng failure is unrecoverable and should panic"); 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. -pub fn derive_key(mut password: SafeCell>, salt: &Salt) -> KeyCell { +pub fn derive_key(password: &mut SafeCell>, salt: &Salt) -> KeyCell { let params = { #[cfg(debug_assertions)] { diff --git a/server/crates/arbiter-server/src/db/models.rs b/server/crates/arbiter-server/src/db/models.rs index 94fdc5b..557d579 100644 --- a/server/crates/arbiter-server/src/db/models.rs +++ b/server/crates/arbiter-server/src/db/models.rs @@ -79,10 +79,41 @@ pub mod types { } } - #[derive(Debug, FromSqlRow, AsExpression, Clone)] - #[diesel(sql_type = Integer)] - #[repr(transparent)] // hint compiler to optimize the wrapper struct away - pub struct ChainId(pub i32); + macro_rules! declare_id { + ($name:ident) => { + #[derive(Debug, FromSqlRow, AsExpression, Clone, Hash, Copy, PartialEq, Eq)] + #[diesel(sql_type = Integer)] + #[repr(transparent)] // hint compiler to optimize the wrapper struct away + pub struct $name(i32); + + impl $name { + pub const fn to_raw(self) -> i32 { + self.0 + } + pub const fn from_raw(raw: i32) -> Self { + Self(raw) + } + } + + impl FromSql for $name { + fn from_sql( + bytes: ::RawValue<'_>, + ) -> diesel::deserialize::Result { + FromSql::::from_sql(bytes).map(Self) + } + } + impl ToSql for $name { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, Sqlite>, + ) -> diesel::serialize::Result { + ToSql::::to_sql(&self.0, out) + } + } + }; + } + + declare_id!(ChainId); #[expect( clippy::cast_sign_loss, @@ -103,21 +134,13 @@ pub mod types { } }; - impl FromSql for ChainId { - fn from_sql( - bytes: ::RawValue<'_>, - ) -> diesel::deserialize::Result { - FromSql::::from_sql(bytes).map(Self) - } - } - impl ToSql for ChainId { - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, Sqlite>, - ) -> diesel::serialize::Result { - ToSql::::to_sql(&self.0, out) - } - } + declare_id!(OperatorId); + declare_id!(OperatorIdentityId); + declare_id!(AeadEncryptedId); + declare_id!(RootKeyHistoryId); + declare_id!(TlsHistoryId); + declare_id!(EvmWalletId); + declare_id!(ClientId); } pub use types::*; @@ -130,12 +153,12 @@ pub use types::*; )] #[diesel(table_name = aead_encrypted, check_for_backend(Sqlite))] pub struct AeadEncrypted { - pub id: i32, + pub id: AeadEncryptedId, pub ciphertext: Vec, pub tag: Vec, pub current_nonce: Vec, pub schema_version: i32, - pub associated_root_key_id: i32, // references root_key_history.id + pub associated_root_key_id: RootKeyHistoryId, pub created_at: SqliteTimestamp, } @@ -148,7 +171,7 @@ pub struct AeadEncrypted { attributes_with = "deriveless" )] pub struct RootKeyHistory { - pub id: i32, + pub id: RootKeyHistoryId, pub ciphertext: Vec, pub tag: Vec, pub root_key_encryption_nonce: Vec, @@ -166,7 +189,7 @@ pub struct RootKeyHistory { attributes_with = "deriveless" )] pub struct TlsHistory { - pub id: i32, + pub id: TlsHistoryId, pub cert: String, pub cert_key: String, // PEM Encoded private key pub ca_cert: String, // PEM Encoded certificate for cert signing @@ -191,7 +214,7 @@ pub struct ArbiterSettings { attributes_with = "deriveless" )] pub struct EvmWallet { - pub id: i32, + pub id: EvmWalletId, pub address: Vec, pub aead_encrypted_id: i32, pub created_at: SqliteTimestamp, @@ -213,7 +236,7 @@ pub struct EvmWallet { )] pub struct EvmWalletAccess { pub id: i32, - pub wallet_id: i32, + pub wallet_id: EvmWalletId, pub client_id: i32, pub created_at: SqliteTimestamp, } @@ -240,7 +263,7 @@ pub struct ProgramClientMetadataHistory { #[derive(Models, Queryable, Debug, Insertable, Selectable)] #[diesel(table_name = schema::program_client, check_for_backend(Sqlite))] pub struct ProgramClient { - pub id: i32, + pub id: ClientId, pub public_key: Vec, pub metadata_id: i32, pub created_at: SqliteTimestamp, @@ -250,12 +273,22 @@ pub struct ProgramClient { #[derive(Queryable, Debug)] #[diesel(table_name = schema::operator_client, check_for_backend(Sqlite))] pub struct OperatorClient { - pub id: i32, + pub id: OperatorIdentityId, pub public_key: Vec, pub created_at: SqliteTimestamp, pub updated_at: SqliteTimestamp, } +#[derive(Queryable, Debug)] +#[diesel(table_name = schema::operator, check_for_backend(Sqlite))] +pub struct Operator { + pub id: OperatorId, + pub share: Vec, + pub share_nonce: Vec, + pub created_at: SqliteTimestamp, + pub updated_at: SqliteTimestamp, +} + #[derive(Models, Queryable, Debug, Insertable, Selectable)] #[diesel(table_name = evm_ether_transfer_limit, check_for_backend(Sqlite))] #[view( @@ -399,7 +432,7 @@ pub struct IntegrityEnvelope { pub entity_kind: String, pub entity_id: Vec, pub payload_version: i32, - pub key_version: i32, + pub key_version: RootKeyHistoryId, pub mac: Vec, pub signed_at: SqliteTimestamp, pub created_at: SqliteTimestamp, diff --git a/server/crates/arbiter-server/src/db/schema.rs b/server/crates/arbiter-server/src/db/schema.rs index 79d6126..c2f9869 100644 --- a/server/crates/arbiter-server/src/db/schema.rs +++ b/server/crates/arbiter-server/src/db/schema.rs @@ -152,6 +152,25 @@ diesel::table! { } } +diesel::table! { + operator (id) { + id -> Nullable, + share -> Binary, + share_nonce -> Binary, + created_at -> Integer, + updated_at -> Integer, + } +} + +diesel::table! { + operator_identity (id) { + id -> Integer, + public_key -> Binary, + created_at -> Integer, + updated_at -> Integer, + } +} + diesel::table! { program_client (id) { id -> Integer, @@ -185,15 +204,6 @@ diesel::table! { } } -diesel::table! { - operator_client (id) { - id -> Integer, - public_key -> Binary, - created_at -> Integer, - updated_at -> Integer, - } -} - diesel::joinable!(aead_encrypted -> root_key_history (associated_root_key_id)); diesel::joinable!(arbiter_settings -> root_key_history (root_key_id)); diesel::joinable!(arbiter_settings -> tls_history (tls_id)); @@ -212,6 +222,7 @@ diesel::joinable!(evm_transaction_log -> evm_wallet_access (wallet_access_id)); diesel::joinable!(evm_wallet -> aead_encrypted (aead_encrypted_id)); diesel::joinable!(evm_wallet_access -> evm_wallet (wallet_id)); diesel::joinable!(evm_wallet_access -> program_client (client_id)); +diesel::joinable!(operator -> operator_identity (id)); diesel::joinable!(program_client -> client_metadata (metadata_id)); diesel::allow_tables_to_appear_in_same_query!( @@ -230,8 +241,9 @@ diesel::allow_tables_to_appear_in_same_query!( evm_wallet, evm_wallet_access, integrity_envelope, + operator, + operator_identity, program_client, root_key_history, tls_history, - operator_client, ); diff --git a/server/crates/arbiter-server/src/evm/mod.rs b/server/crates/arbiter-server/src/evm/mod.rs index 20154ad..5d05ca4 100644 --- a/server/crates/arbiter-server/src/evm/mod.rs +++ b/server/crates/arbiter-server/src/evm/mod.rs @@ -359,7 +359,8 @@ mod tests { use crate::db::{ self, DatabaseConnection, models::{ - EvmBasicGrant, EvmWalletAccess, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp, + EvmBasicGrant, EvmWalletAccess, EvmWalletId, NewEvmBasicGrant, NewEvmTransactionLog, + SqliteTimestamp, }, schema::{evm_basic_grant, evm_transaction_log}, }; @@ -377,7 +378,7 @@ mod tests { EvalContext { target: EvmWalletAccess { id: WALLET_ACCESS_ID, - wallet_id: 10, + wallet_id: EvmWalletId::from_raw(5), client_id: 20, created_at: SqliteTimestamp(Utc::now()), }, diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs index 80bd3ba..b9deb99 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs @@ -3,7 +3,8 @@ use crate::{ db::{ self, DatabaseConnection, models::{ - EvmBasicGrant, EvmWalletAccess, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp, + EvmBasicGrant, EvmWalletAccess, EvmWalletId, NewEvmBasicGrant, NewEvmTransactionLog, + SqliteTimestamp, }, schema::{evm_basic_grant, evm_transaction_log}, }, @@ -31,7 +32,7 @@ fn ctx(to: Address, value: U256) -> EvalContext { EvalContext { target: EvmWalletAccess { id: WALLET_ACCESS_ID, - wallet_id: 10, + wallet_id: EvmWalletId::from_raw(10), client_id: 20, created_at: SqliteTimestamp(Utc::now()), }, diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs index 0ea8c9c..f2c02b3 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs @@ -2,7 +2,7 @@ use super::{Settings, TokenTransfer}; use crate::{ db::{ self, DatabaseConnection, - models::{EvmBasicGrant, EvmWalletAccess, NewEvmBasicGrant, SqliteTimestamp}, + models::{EvmBasicGrant, EvmWalletAccess, EvmWalletId, NewEvmBasicGrant, SqliteTimestamp}, schema::evm_basic_grant, }, evm::{ @@ -45,7 +45,7 @@ fn ctx(to: Address, calldata: Bytes) -> EvalContext { EvalContext { target: EvmWalletAccess { id: WALLET_ACCESS_ID, - wallet_id: 10, + wallet_id: EvmWalletId::from_raw(10), client_id: 20, created_at: SqliteTimestamp(Utc::now()), }, diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs index 02597a8..300b142 100644 --- a/server/crates/arbiter-server/src/evm/safe_signer.rs +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -44,7 +44,7 @@ impl std::fmt::Debug for SafeSigner { /// Returns the protected key bytes and the derived Ethereum address. pub fn generate(rng: &mut impl rand::Rng) -> (SafeCell<[u8; 32]>, Address) { 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); }); diff --git a/server/crates/arbiter-server/src/grpc/client/vault.rs b/server/crates/arbiter-server/src/grpc/client/vault.rs index f5561b9..95d4ba8 100644 --- a/server/crates/arbiter-server/src/grpc/client/vault.rs +++ b/server/crates/arbiter-server/src/grpc/client/vault.rs @@ -31,6 +31,7 @@ pub(super) async fn dispatch( VaultRequestPayload::QueryState(()) => { let state = match actor.ask(HandleQueryVaultState {}).await { Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, + Ok(VaultState::Bootstrapping) => ProtoVaultState::Boostrapping, Ok(VaultState::Sealed) => ProtoVaultState::Sealed, Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed, Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error, diff --git a/server/crates/arbiter-server/src/grpc/operator/evm.rs b/server/crates/arbiter-server/src/grpc/operator/evm.rs index 0b3ac2c..9578c6e 100644 --- a/server/crates/arbiter-server/src/grpc/operator/evm.rs +++ b/server/crates/arbiter-server/src/grpc/operator/evm.rs @@ -90,7 +90,7 @@ async fn handle_wallet_list( .into_iter() .map(|(id, address)| WalletEntry { address: address.to_vec(), - id, + id: id.to_raw(), }) .collect(), }), diff --git a/server/crates/arbiter-server/src/grpc/operator/inbound.rs b/server/crates/arbiter-server/src/grpc/operator/inbound.rs index 7e2c87e..dc62d4f 100644 --- a/server/crates/arbiter-server/src/grpc/operator/inbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/inbound.rs @@ -1,11 +1,10 @@ use crate::{ - db::models::{CoreEvmWalletAccess, NewEvmWalletAccess}, + db::models::{CoreEvmWalletAccess, EvmWalletId, NewEvmWalletAccess}, evm::policies::{ SharedGrantSettings, SpecificGrant, TransactionRateLimit, VolumeRateLimit, ether_transfer, token_transfers, }, - grpc::Convert, - grpc::TryConvert, + grpc::{Convert, TryConvert}, }; use arbiter_proto::{ proto::evm::{ @@ -150,7 +149,7 @@ impl Convert for WalletAccess { fn convert(self) -> Self::Output { NewEvmWalletAccess { - wallet_id: self.wallet_id, + wallet_id: EvmWalletId::from_raw(self.wallet_id), client_id: self.sdk_client_id, } } @@ -165,7 +164,7 @@ impl TryConvert for SdkClientWalletAccess { return Err(Status::invalid_argument("Missing wallet access entry")); }; Ok(CoreEvmWalletAccess { - wallet_id: access.wallet_id, + wallet_id: EvmWalletId::from_raw(access.wallet_id), client_id: access.sdk_client_id, id: self.id, }) diff --git a/server/crates/arbiter-server/src/grpc/operator/outbound.rs b/server/crates/arbiter-server/src/grpc/operator/outbound.rs index a4f4c29..4031915 100644 --- a/server/crates/arbiter-server/src/grpc/operator/outbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/outbound.rs @@ -103,7 +103,7 @@ impl Convert for EvmWalletAccess { Self::Output { id: self.id, access: Some(WalletAccess { - wallet_id: self.wallet_id, + wallet_id: self.wallet_id.to_raw(), sdk_client_id: self.client_id, }), } diff --git a/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs b/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs index b88a73e..777e567 100644 --- a/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs +++ b/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs @@ -2,7 +2,7 @@ use crate::{ db::models::NewEvmWalletAccess, grpc::Convert, peers::operator::{ - OutOfBand, OperatorSession, + OperatorSession, OutOfBand, session::handlers::{ HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove, HandleRevokeEvmWalletAccess, HandleSdkClientList, @@ -11,8 +11,8 @@ use crate::{ }; use arbiter_crypto::authn; use arbiter_proto::proto::{ - shared::ClientInfo as ProtoClientMetadata, operator::{ + operator_response::Payload as OperatorResponsePayload, sdk_client::{ self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel, ConnectionRequest as ProtoSdkClientConnectionRequest, @@ -24,8 +24,8 @@ use arbiter_proto::proto::{ request::Payload as SdkClientRequestPayload, response::Payload as SdkClientResponsePayload, }, - operator_response::Payload as OperatorResponsePayload, }, + shared::ClientInfo as ProtoClientMetadata, }; use kameo::actor::ActorRef; @@ -115,7 +115,7 @@ async fn handle_list( clients: clients .into_iter() .map(|(client, metadata)| ProtoSdkClientEntry { - id: client.id, + id: client.id.to_raw(), pubkey: client.public_key.clone(), info: Some(ProtoClientMetadata { name: metadata.name, diff --git a/server/crates/arbiter-server/src/grpc/operator/vault.rs b/server/crates/arbiter-server/src/grpc/operator/vault.rs index ac1c293..582ab13 100644 --- a/server/crates/arbiter-server/src/grpc/operator/vault.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault.rs @@ -3,7 +3,6 @@ use crate::{ peers::operator::{OperatorSession, session::handlers::HandleQueryVaultState}, }; use arbiter_proto::{ - proto::shared::VaultState as ProtoVaultState, proto::operator::{ operator_response::Payload as OperatorResponsePayload, vault::{ @@ -11,6 +10,7 @@ use arbiter_proto::{ response::Payload as VaultResponsePayload, }, }, + proto::shared::VaultState as ProtoVaultState, }; use kameo::actor::ActorRef; @@ -47,6 +47,7 @@ async fn handle_query_vault_state( let state = match actor.ask(HandleQueryVaultState {}).await { Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, Ok(VaultState::Sealed) => ProtoVaultState::Sealed, + Ok(VaultState::Bootstrapping) => ProtoVaultState::Boostrapping, Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed, Err(err) => { warn!(error = ?err, "Failed to query vault state"); diff --git a/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs index 4a2f072..268b7d5 100644 --- a/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs @@ -4,7 +4,6 @@ use crate::{ peers::operator::vault_gate::{self as vault_gate}, }; use arbiter_proto::proto::{ - shared::VaultState as ProtoVaultState, operator::{ operator_response::Payload as OperatorResponsePayload, vault::{ @@ -17,6 +16,7 @@ use arbiter_proto::proto::{ }, }, }, + shared::VaultState as ProtoVaultState, }; use tonic::Status; @@ -46,6 +46,7 @@ impl Convert for VaultState { fn convert(self) -> OperatorResponsePayload { let proto_state = match self { Self::Unbootstrapped => ProtoVaultState::Unbootstrapped, + Self::Bootstrapping => ProtoVaultState::Boostrapping, Self::Sealed => ProtoVaultState::Sealed, Self::Unsealed => ProtoVaultState::Unsealed, }; diff --git a/server/crates/arbiter-server/src/peers/operator/auth/state.rs b/server/crates/arbiter-server/src/peers/operator/auth/state.rs index d4033f5..9862612 100644 --- a/server/crates/arbiter-server/src/peers/operator/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/operator/auth/state.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ actors::bootstrap::ConsumeToken, - db::{DatabasePool, schema::operator_client}, + db::{DatabasePool, schema::operator_identity}, peers::operator::auth::Outbound, }; use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT}; @@ -44,9 +44,9 @@ async fn get_client_id(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result(&mut conn) .await .optional() @@ -63,9 +63,9 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result Result, Error> { + pub(crate) async fn handle_evm_wallet_list( + &mut self, + ) -> Result, Error> { match self.props.actors.evm.ask(ListWallets {}).await { Ok(wallets) => Ok(wallets), Err(err) => { diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 90763a8..a7320f6 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -86,8 +86,8 @@ async fn insert_bootstrap_sentinel_operator(db: &db::DatabasePool) { .0 .to_vec(); - insert_into(schema::operator_client::table) - .values((schema::operator_client::public_key.eq(sentinel_key),)) + insert_into(schema::operator_identity::table) + .values((schema::operator_identity::public_key.eq(sentinel_key),)) .execute(&mut conn) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/operator/auth.rs b/server/crates/arbiter-server/tests/operator/auth.rs index e9e585d..433c03d 100644 --- a/server/crates/arbiter-server/tests/operator/auth.rs +++ b/server/crates/arbiter-server/tests/operator/auth.rs @@ -206,8 +206,8 @@ pub async fn bootstrap_token_auth() { task.await.unwrap().unwrap(); let mut conn = db.get().await.unwrap(); - let stored_pubkey: Vec = schema::operator_client::table - .select(schema::operator_client::public_key) + let stored_pubkey: Vec = schema::operator_identity::table + .select(schema::operator_identity::public_key) .first::>(&mut conn) .await .unwrap(); @@ -259,7 +259,7 @@ pub async fn bootstrap_invalid_token_auth() { )); let mut conn = db.get().await.unwrap(); - let count: i64 = schema::operator_client::table + let count: i64 = schema::operator_identity::table .count() .get_result::(&mut conn) .await @@ -285,9 +285,9 @@ pub async fn challenge_auth() { { let mut conn = db.get().await.unwrap(); - let id: i32 = insert_into(schema::operator_client::table) - .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) - .returning(schema::operator_client::id) + let id: i32 = insert_into(schema::operator_identity::table) + .values((schema::operator_identity::public_key.eq(pubkey_bytes.clone()),)) + .returning(schema::operator_identity::id) .get_result(&mut conn) .await .unwrap(); @@ -371,8 +371,8 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { { let mut conn = db.get().await.unwrap(); - insert_into(schema::operator_client::table) - .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) + insert_into(schema::operator_identity::table) + .values((schema::operator_identity::public_key.eq(pubkey_bytes.clone()),)) .execute(&mut conn) .await .unwrap(); @@ -444,9 +444,9 @@ pub async fn challenge_auth_rejects_invalid_signature() { { let mut conn = db.get().await.unwrap(); - let id: i32 = insert_into(schema::operator_client::table) - .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) - .returning(schema::operator_client::id) + let id: i32 = insert_into(schema::operator_identity::table) + .values((schema::operator_identity::public_key.eq(pubkey_bytes.clone()),)) + .returning(schema::operator_identity::id) .get_result(&mut conn) .await .unwrap();