From 1585f90cae0af5e6bfe170b48c9b5ffe0fa83151 Mon Sep 17 00:00:00 2001 From: hdbg Date: Tue, 7 Apr 2026 23:54:29 +0200 Subject: [PATCH 01/13] refactor(server): reorganized client/user_agent actors into separate module `peers` and added event `MessageBus` --- AGENTS.md | 2 +- CLAUDE.md | 2 +- server/Cargo.lock | 24 ++- server/Cargo.toml | 2 +- server/crates/arbiter-server/Cargo.toml | 1 + .../arbiter-server/src/actors/evm/mod.rs | 36 ++--- .../client_connect_approval.rs | 7 +- .../src/actors/flow_coordinator/mod.rs | 6 +- .../crates/arbiter-server/src/actors/mod.rs | 26 ++-- .../src/actors/{keyholder => vault}/mod.rs | 138 ++++++++++-------- .../arbiter-server/src/crypto/integrity/v1.rs | 71 ++++----- server/crates/arbiter-server/src/evm/mod.rs | 16 +- .../crates/arbiter-server/src/grpc/client.rs | 2 +- .../arbiter-server/src/grpc/client/auth.rs | 2 +- .../arbiter-server/src/grpc/client/evm.rs | 2 +- .../arbiter-server/src/grpc/client/vault.rs | 12 +- server/crates/arbiter-server/src/grpc/mod.rs | 2 +- .../arbiter-server/src/grpc/user_agent.rs | 2 +- .../src/grpc/user_agent/auth.rs | 2 +- .../arbiter-server/src/grpc/user_agent/evm.rs | 2 +- .../src/grpc/user_agent/sdk_client.rs | 2 +- .../src/grpc/user_agent/vault.rs | 12 +- server/crates/arbiter-server/src/lib.rs | 1 + .../src/{actors => peers}/client/auth.rs | 30 ++-- .../src/{actors => peers}/client/mod.rs | 4 +- .../src/{actors => peers}/client/session.rs | 13 +- server/crates/arbiter-server/src/peers/mod.rs | 2 + .../src/{actors => peers}/user_agent/auth.rs | 6 +- .../user_agent/auth/state.rs | 23 +-- .../src/{actors => peers}/user_agent/mod.rs | 4 +- .../{actors => peers}/user_agent/session.rs | 8 +- .../user_agent/session/connection.rs | 43 +++--- .../user_agent/session/state.rs | 0 .../arbiter-server/tests/client/auth.rs | 9 +- .../crates/arbiter-server/tests/common/mod.rs | 6 +- .../crates/arbiter-server/tests/keyholder.rs | 8 - .../arbiter-server/tests/user_agent/auth.rs | 17 ++- .../arbiter-server/tests/user_agent/unseal.rs | 11 +- server/crates/arbiter-server/tests/vault.rs | 8 + .../tests/{keyholder => vault}/concurrency.rs | 14 +- .../tests/{keyholder => vault}/lifecycle.rs | 26 ++-- .../tests/{keyholder => vault}/storage.rs | 14 +- 42 files changed, 332 insertions(+), 286 deletions(-) rename server/crates/arbiter-server/src/actors/{keyholder => vault}/mod.rs (84%) rename server/crates/arbiter-server/src/{actors => peers}/client/auth.rs (94%) rename server/crates/arbiter-server/src/{actors => peers}/client/mod.rs (95%) rename server/crates/arbiter-server/src/{actors => peers}/client/session.rs (89%) create mode 100644 server/crates/arbiter-server/src/peers/mod.rs rename server/crates/arbiter-server/src/{actors => peers}/user_agent/auth.rs (96%) rename server/crates/arbiter-server/src/{actors => peers}/user_agent/auth/state.rs (94%) rename server/crates/arbiter-server/src/{actors => peers}/user_agent/mod.rs (95%) rename server/crates/arbiter-server/src/{actors => peers}/user_agent/session.rs (96%) rename server/crates/arbiter-server/src/{actors => peers}/user_agent/session/connection.rs (93%) rename server/crates/arbiter-server/src/{actors => peers}/user_agent/session/state.rs (100%) delete mode 100644 server/crates/arbiter-server/tests/keyholder.rs create mode 100644 server/crates/arbiter-server/tests/vault.rs rename server/crates/arbiter-server/tests/{keyholder => vault}/concurrency.rs (91%) rename server/crates/arbiter-server/tests/{keyholder => vault}/lifecycle.rs (76%) rename server/crates/arbiter-server/tests/{keyholder => vault}/storage.rs (91%) diff --git a/AGENTS.md b/AGENTS.md index fb2d230..d935f50 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,7 +66,7 @@ cargo insta review The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: - **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. -- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. +- **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. - **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients. - **`EvmActor`** — Handles EVM transaction policy enforcement and signing. diff --git a/CLAUDE.md b/CLAUDE.md index c3c3357..8305248 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,7 +66,7 @@ cargo insta review The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: - **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. -- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. +- **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. - **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients. - **`EvmActor`** — Handles EVM transaction policy enforcement and signing. diff --git a/server/Cargo.lock b/server/Cargo.lock index 5b89b7e..d692f5b 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -753,6 +753,7 @@ dependencies = [ "insta", "k256", "kameo", + "kameo_actors", "ml-dsa", "mutants", "pem", @@ -2962,9 +2963,9 @@ dependencies = [ [[package]] name = "kameo" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4af7638c67029fd6821d02813c3913c803784648725d4df4082c9b91d7cbb1" +checksum = "b1dfd134d7a2c6ec05ee696dcbf3f7a034bdb97ecc623e981014652dcd124d77" dependencies = [ "downcast-rs", "dyn-clone", @@ -2976,10 +2977,23 @@ dependencies = [ ] [[package]] -name = "kameo_macros" -version = "0.19.0" +name = "kameo_actors" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13c324e2d8c8e126e63e66087448b4267e263e6cb8770c56d10a9d0d279d9e2" +checksum = "220bdd75769f0a9b752a91123e58bf42f595e3c36ff4b13818d7a87d962076e6" +dependencies = [ + "futures", + "glob", + "kameo", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "kameo_macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c9002c9ecd16e1636f566c0bf62db48e70d86ed0d0a91b398955e883217a23" dependencies = [ "heck", "proc-macro2", diff --git a/server/Cargo.toml b/server/Cargo.toml index 98018a0..7727573 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,7 +26,7 @@ thiserror = "2.0.18" async-trait = "0.1.89" futures = "0.3.32" tokio-stream = { version = "0.1.18", features = ["full"] } -kameo = "0.19.2" +kameo = "0.20" prost-types = { version = "0.14.3", features = ["chrono"] } x25519-dalek = { version = "2.0.1", features = ["getrandom"] } rstest = "0.26.1" diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index c563ee0..f7272f6 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -58,6 +58,7 @@ ml-dsa.workspace = true ed25519-dalek.workspace = true x25519-dalek.workspace = true k256.workspace = true +kameo_actors = "0.5.0" [dev-dependencies] insta = "1.46.3" diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index c31cdd0..5520ca1 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -7,7 +7,7 @@ use kameo::{Actor, actor::ActorRef, messages}; use rand::{SeedableRng, rng, rngs::StdRng}; use crate::{ - actors::keyholder::{CreateNew, Decrypt, KeyHolder}, + actors::vault::{CreateNew, Decrypt, Vault}, crypto::integrity, db::{ DatabaseError, DatabasePool, @@ -34,11 +34,11 @@ pub enum SignTransactionError { #[error("Database error: {0}")] Database(#[from] DatabaseError), - #[error("Keyholder error: {0}")] - Keyholder(#[from] crate::actors::keyholder::Error), + #[error("Vault error: {0}")] + Vault(#[from] crate::actors::vault::Error), - #[error("Keyholder mailbox error")] - KeyholderSend, + #[error("Vault mailbox error")] + VaultSend, #[error("Signing error: {0}")] Signing(#[from] alloy::signers::Error), @@ -49,11 +49,11 @@ pub enum SignTransactionError { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Keyholder error: {0}")] - Keyholder(#[from] crate::actors::keyholder::Error), + #[error("Vault error: {0}")] + Vault(#[from] crate::actors::vault::Error), - #[error("Keyholder mailbox error")] - KeyholderSend, + #[error("Vault mailbox error")] + VaultSend, #[error("Database error: {0}")] Database(#[from] DatabaseError), @@ -64,20 +64,20 @@ pub enum Error { #[derive(Actor)] pub struct EvmActor { - pub keyholder: ActorRef, + pub vault: ActorRef, pub db: DatabasePool, pub rng: StdRng, pub engine: evm::Engine, } impl EvmActor { - pub fn new(keyholder: ActorRef, db: DatabasePool) -> Self { + pub fn new(vault: ActorRef, db: DatabasePool) -> Self { // is it safe to seed rng from system once? // todo: audit let rng = StdRng::from_rng(&mut rng()); - let engine = evm::Engine::new(db.clone(), keyholder.clone()); + let engine = evm::Engine::new(db.clone(), vault.clone()); Self { - keyholder, + vault, db, rng, engine, @@ -94,10 +94,10 @@ impl EvmActor { let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec())); let aead_id: i32 = self - .keyholder + .vault .ask(CreateNew { plaintext }) .await - .map_err(|_| Error::KeyholderSend)?; + .map_err(|_| Error::VaultSend)?; let mut conn = self.db.get().await.map_err(DatabaseError::from)?; let wallet_id = insert_into(schema::evm_wallet::table) @@ -160,7 +160,7 @@ impl EvmActor { #[message] pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> { // let mut conn = self.db.get().await.map_err(DatabaseError::from)?; - // let keyholder = self.keyholder.clone(); + // let vault = self.vault.clone(); // diesel_async::AsyncConnection::transaction(&mut conn, |conn| { // Box::pin(async move { @@ -254,12 +254,12 @@ impl EvmActor { drop(conn); let raw_key: SafeCell> = self - .keyholder + .vault .ask(Decrypt { aead_id: wallet.aead_encrypted_id, }) .await - .map_err(|_| SignTransactionError::KeyholderSend)?; + .map_err(|_| SignTransactionError::VaultSend)?; let signer = safe_signer::SafeSigner::from_cell(raw_key)?; diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs index c5b20c3..293a463 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs @@ -6,10 +6,9 @@ use kameo::{ reply::ReplySender, }; -use crate::actors::{ - client::ClientProfile, - flow_coordinator::ApprovalError, - user_agent::{UserAgentSession, session::BeginNewClientApproval}, +use crate::{ + actors::flow_coordinator::ApprovalError, + peers::{client::ClientProfile, user_agent::{UserAgentSession, session::BeginNewClientApproval}}, }; pub struct Args { diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs index 2e0aa9a..7261f7f 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs @@ -9,11 +9,7 @@ use kameo::{ }; use tracing::info; -use crate::actors::{ - client::{ClientProfile, session::ClientSession}, - flow_coordinator::client_connect_approval::ClientApprovalController, - user_agent::session::UserAgentSession, -}; +use crate::{actors::flow_coordinator::client_connect_approval::ClientApprovalController, peers::{client::{ClientProfile, session::ClientSession}, user_agent::UserAgentSession}}; pub mod client_connect_approval; diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index 8ff1fce..155d34d 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -1,47 +1,53 @@ use kameo::actor::{ActorRef, Spawn}; +use kameo_actors::{DeliveryStrategy, message_bus::MessageBus}; use thiserror::Error; use crate::{ actors::{ bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, - keyholder::KeyHolder, + vault::Vault, }, db, }; pub mod bootstrap; -pub mod client; -mod evm; +pub mod evm; pub mod flow_coordinator; -pub mod keyholder; -pub mod user_agent; +pub mod vault; #[derive(Error, Debug)] pub enum SpawnError { #[error("Failed to spawn Bootstrapper actor")] Bootstrapper(#[from] bootstrap::Error), - #[error("Failed to spawn KeyHolder actor")] - KeyHolder(#[from] keyholder::Error), + #[error("Failed to spawn Vault actor")] + Vault(#[from] vault::Error), } /// Long-lived actors that are shared across all connections and handle global state and operations #[derive(Clone)] pub struct GlobalActors { - pub key_holder: ActorRef, + pub vault: ActorRef, pub bootstrapper: ActorRef, pub flow_coordinator: ActorRef, pub evm: ActorRef, + pub events: ActorRef, } impl GlobalActors { + pub fn spawn_message_bus() -> ActorRef { + MessageBus::spawn(MessageBus::new(DeliveryStrategy::Guaranteed)) + } + pub async fn spawn(db: db::DatabasePool) -> Result { - let key_holder = KeyHolder::spawn(KeyHolder::new(db.clone()).await?); + let message_bus = Self::spawn_message_bus(); + let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?); Ok(Self { bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?), evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)), - key_holder, + vault: key_holder, flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::default()), + events: message_bus, }) } } diff --git a/server/crates/arbiter-server/src/actors/keyholder/mod.rs b/server/crates/arbiter-server/src/actors/vault/mod.rs similarity index 84% rename from server/crates/arbiter-server/src/actors/keyholder/mod.rs rename to server/crates/arbiter-server/src/actors/vault/mod.rs index 64387bc..7a4f456 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault/mod.rs @@ -5,7 +5,8 @@ use diesel::{ }; use diesel_async::{AsyncConnection, RunQueryDsl}; use hmac::Mac as _; -use kameo::{Actor, Reply, messages}; +use kameo::{Actor, Reply, actor::ActorRef, messages}; +use kameo_actors::message_bus::{MessageBus, Publish}; use strum::{EnumDiscriminants, IntoDiscriminant}; use tracing::{error, info}; @@ -21,26 +22,26 @@ use crate::db::{ }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; -#[derive(Default, EnumDiscriminants)] -#[strum_discriminants(derive(Reply), vis(pub), name(KeyHolderState))] -enum State { - #[default] - Unbootstrapped, - Sealed { - root_key_history_id: i32, - }, - Unsealed { - root_key_history_id: i32, - root_key: KeyCell, - }, +pub mod events { + + #[derive(Clone, Copy)] + pub struct VaultBootstrapped; + + #[derive(Clone, Copy)] + pub struct VaultUnsealed; + + #[derive(Clone, Copy)] + pub struct VaultResealed; } #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Keyholder is already bootstrapped")] + #[error("Vault is already bootstrapped")] AlreadyBootstrapped, - #[error("Keyholder is not bootstrapped")] + #[error("Vault is not bootstrapped")] NotBootstrapped, + #[error("Vault is sealed")] + Sealed, #[error("Invalid key provided")] InvalidKey, @@ -60,18 +61,35 @@ pub enum Error { BrokenDatabase, } +struct Unsealed { + root_key_history_id: i32, + root_key: KeyCell, +} + +#[derive(Default, EnumDiscriminants)] +#[strum_discriminants(derive(Reply), vis(pub), name(VaultState))] +enum State { + #[default] + Unbootstrapped, + Sealed { + root_key_history_id: i32, + }, + Unsealed(Unsealed), +} + /// Manages vault root key and tracks current state of the vault (bootstrapped/unbootstrapped, sealed/unsealed). /// Provides API for encrypting and decrypting data using the vault root key. /// Abstraction over database to make sure nonces are never reused and encryption keys are never exposed in plaintext outside of this actor. #[derive(Actor)] -pub struct KeyHolder { +pub struct Vault { db: db::DatabasePool, state: State, + events: ActorRef, } #[messages] -impl KeyHolder { - pub async fn new(db: db::DatabasePool) -> Result { +impl Vault { + pub async fn new(db: db::DatabasePool, events: ActorRef) -> Result { let state = { let mut conn = db.get().await?; @@ -89,10 +107,10 @@ impl KeyHolder { } }; - Ok(Self { db, state }) + Ok(Self { db, state, events }) } - // Exclusive transaction to avoid race condtions if multiple keyholders write + // 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 { let mut conn = pool.get().await?; @@ -129,6 +147,14 @@ impl KeyHolder { Ok(nonce) } + fn expect_unsealed<'a>(state: &'a mut State) -> Result<&'a mut Unsealed, Error> { + match state { + State::Unsealed(unsealed) => Ok(unsealed), + 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) { @@ -181,12 +207,13 @@ impl KeyHolder { }) .await?; - self.state = State::Unsealed { + self.state = State::Unsealed(Unsealed { root_key, root_key_history_id, - }; + }); - info!("Keyholder bootstrapped successfully"); + info!("Vault bootstrapped successfully"); + self.events.tell(Publish(events::VaultBootstrapped)).await; Ok(()) } @@ -233,24 +260,23 @@ impl KeyHolder { Error::InvalidKey })?; - self.state = State::Unsealed { + self.state = State::Unsealed(Unsealed { root_key_history_id: current_key.id, root_key: KeyCell::try_from(root_key).map_err(|err| { error!(?err, "Broken database: invalid encryption key size"); Error::BrokenDatabase })?, - }; + }); - info!("Keyholder unsealed successfully"); + info!("Vault unsealed successfully"); + self.events.tell(Publish(events::VaultUnsealed)).await; Ok(()) } #[message] pub async fn decrypt(&mut self, aead_id: i32) -> Result>, Error> { - let State::Unsealed { root_key, .. } = &mut self.state else { - return Err(Error::NotBootstrapped); - }; + let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?; let row: models::AeadEncrypted = { let mut conn = self.db.get().await?; @@ -278,14 +304,10 @@ impl KeyHolder { // Creates new `aead_encrypted` entry in the database and returns it's ID #[message] pub async fn create_new(&mut self, mut plaintext: SafeCell>) -> Result { - let State::Unsealed { + let Unsealed { root_key, root_key_history_id, - .. - } = &mut self.state - else { - return Err(Error::NotBootstrapped); - }; + } = Self::expect_unsealed(&mut self.state)?; // Order matters here - `get_new_nonce` acquires connection, so we need to call it before next acquire // Borrow checker note: &mut borrow a few lines above is disjoint from this field @@ -315,19 +337,16 @@ impl KeyHolder { } #[message] - pub fn get_state(&self) -> KeyHolderState { + pub fn get_state(&self) -> VaultState { self.state.discriminant() } #[message] pub fn sign_integrity(&mut self, mac_input: Vec) -> Result<(i32, Vec), Error> { - let State::Unsealed { + let Unsealed { root_key, root_key_history_id, - } = &mut self.state - else { - return Err(Error::NotBootstrapped); - }; + } = Self::expect_unsealed(&mut self.state)?; let mut hmac = root_key .0 @@ -349,13 +368,10 @@ impl KeyHolder { expected_mac: Vec, key_version: i32, ) -> Result { - let State::Unsealed { + let Unsealed { root_key, root_key_history_id, - } = &mut self.state - else { - return Err(Error::NotBootstrapped); - }; + } = Self::expect_unsealed(&mut self.state)?; if *root_key_history_id != key_version { return Ok(false); @@ -374,17 +390,16 @@ impl KeyHolder { } #[message] - pub fn seal(&mut self) -> Result<(), Error> { - let State::Unsealed { + pub async fn seal(&mut self) -> Result<(), Error> { + let Unsealed { root_key_history_id, .. - } = &self.state - else { - return Err(Error::NotBootstrapped); - }; + } = Self::expect_unsealed(&mut self.state)?; + self.state = State::Sealed { root_key_history_id: *root_key_history_id, }; + self.events.tell(Publish(events::VaultResealed)).await; Ok(()) } } @@ -395,13 +410,18 @@ mod tests { use diesel_async::RunQueryDsl; - use crate::db::{self}; + use crate::{ + actors::GlobalActors, + db::{self}, + }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use super::*; - async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder { - let mut actor = KeyHolder::new(db.clone()).await.unwrap(); + async fn bootstrapped_actor(db: &db::DatabasePool) -> Vault { + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.bootstrap(seal_key).await.unwrap(); actor @@ -413,17 +433,17 @@ mod tests { let db = db::create_test_pool().await; let mut actor = bootstrapped_actor(&db).await; let root_key_history_id = match actor.state { - State::Unsealed { + State::Unsealed(Unsealed { root_key_history_id, .. - } => root_key_history_id, + }) => root_key_history_id, _ => panic!("expected unsealed state"), }; - let n1 = KeyHolder::get_new_nonce(&db, root_key_history_id) + let n1 = Vault::get_new_nonce(&db, root_key_history_id) .await .unwrap(); - let n2 = KeyHolder::get_new_nonce(&db, root_key_history_id) + let n2 = Vault::get_new_nonce(&db, root_key_history_id) .await .unwrap(); assert!(n2.to_vec() > n1.to_vec(), "nonce must increase"); diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index 4b67217..87199d3 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -1,4 +1,4 @@ -use crate::{actors::keyholder, crypto::integrity::hashing::Hashable}; +use crate::{actors::vault, crypto::integrity::hashing::Hashable}; use arbiter_crypto::safecell::SafeCellHandle as _; use hmac::{Hmac, Mac as _}; use sha2::Sha256; @@ -11,7 +11,7 @@ use sha2::Digest as _; pub mod hashing; use crate::{ - actors::keyholder::{KeyHolder, SignIntegrity, VerifyIntegrity}, + actors::vault::{SignIntegrity, Vault, VerifyIntegrity}, db::{ self, models::{IntegrityEnvelope, NewIntegrityEnvelope}, @@ -24,11 +24,11 @@ pub enum Error { #[error("Database error: {0}")] Database(#[from] db::DatabaseError), - #[error("KeyHolder error: {0}")] - Keyholder(#[from] keyholder::Error), + #[error("Vault error: {0}")] + Vault(#[from] vault::Error), - #[error("KeyHolder mailbox error")] - KeyholderSend, + #[error("Vault mailbox error")] + VaultSend, #[error("Integrity envelope is missing for entity {entity_kind}")] MissingEnvelope { entity_kind: &'static str }, @@ -105,7 +105,7 @@ impl IntoId for &'_ [u8] { pub async fn sign_entity( conn: &mut impl AsyncConnection, - keyholder: &ActorRef, + vault: &ActorRef, entity: &E, entity_id: impl IntoId, ) -> Result<(), Error> { @@ -115,13 +115,14 @@ pub async fn sign_entity( let mac_input = build_mac_input(E::KIND, &entity_id, E::VERSION, &payload_hash); - let (key_version, mac) = keyholder - .ask(SignIntegrity { mac_input }) - .await - .map_err(|err| match err { - kameo::error::SendError::HandlerError(inner) => Error::Keyholder(inner), - _ => Error::KeyholderSend, - })?; + let (key_version, mac) = + vault + .ask(SignIntegrity { mac_input }) + .await + .map_err(|err| match err { + kameo::error::SendError::HandlerError(inner) => Error::Vault(inner), + _ => Error::VaultSend, + })?; insert_into(integrity_envelope::table) .values(NewIntegrityEnvelope { @@ -150,7 +151,7 @@ pub async fn sign_entity( pub async fn verify_entity( conn: &mut impl AsyncConnection, - keyholder: &ActorRef, + vault: &ActorRef, entity: &E, entity_id: impl IntoId, ) -> Result { @@ -178,7 +179,7 @@ pub async fn verify_entity( let payload_hash = payload_hash(&entity); let mac_input = build_mac_input(E::KIND, &entity_id, envelope.payload_version, &payload_hash); - let result = keyholder + let result = vault .ask(VerifyIntegrity { mac_input, expected_mac: envelope.mac, @@ -191,10 +192,10 @@ pub async fn verify_entity( Ok(false) => Err(Error::MacMismatch { entity_kind: E::KIND, }), - Err(SendError::HandlerError(keyholder::Error::NotBootstrapped)) => { + Err(SendError::HandlerError(vault::Error::NotBootstrapped)) => { Ok(AttestationStatus::Unavailable) } - Err(_) => Err(Error::KeyholderSend), + Err(_) => Err(Error::VaultSend), } } @@ -203,19 +204,17 @@ mod tests { use diesel::{ExpressionMethods as _, QueryDsl}; use diesel_async::RunQueryDsl; use kameo::{actor::ActorRef, prelude::Spawn}; - + use sha2::Digest; - - use crate::{ - actors::keyholder::{Bootstrap, KeyHolder}, + actors::{GlobalActors, vault::{Bootstrap, Vault}}, db::{self, schema}, }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; - use super::{Error, Integrable, sign_entity, verify_entity}; use super::hashing::Hashable; + use super::{Error, Integrable, sign_entity, verify_entity}; #[derive(Clone)] struct DummyEntity { @@ -233,8 +232,12 @@ mod tests { const KIND: &'static str = "dummy_entity"; } - async fn bootstrapped_keyholder(db: &db::DatabasePool) -> ActorRef { - let actor = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap()); + async fn bootstrapped_vault(db: &db::DatabasePool) -> ActorRef { + let actor = Vault::spawn( + Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(), + ); actor .ask(Bootstrap { seal_key_raw: SafeCell::new(b"integrity-test-seal-key".to_vec()), @@ -247,7 +250,7 @@ mod tests { #[tokio::test] async fn sign_writes_envelope_and_verify_passes() { let db = db::create_test_pool().await; - let keyholder = bootstrapped_keyholder(&db).await; + let vault = bootstrapped_vault(&db).await; let mut conn = db.get().await.unwrap(); const ENTITY_ID: &[u8] = b"entity-id-7"; @@ -257,7 +260,7 @@ mod tests { payload: b"payload-v1".to_vec(), }; - sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) + sign_entity(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap(); @@ -270,7 +273,7 @@ mod tests { .unwrap(); assert_eq!(count, 1, "envelope row must be created exactly once"); - verify_entity(&mut conn, &keyholder, &entity, ENTITY_ID) + verify_entity(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap(); } @@ -278,7 +281,7 @@ mod tests { #[tokio::test] async fn tampered_mac_fails_verification() { let db = db::create_test_pool().await; - let keyholder = bootstrapped_keyholder(&db).await; + let vault = bootstrapped_vault(&db).await; let mut conn = db.get().await.unwrap(); const ENTITY_ID: &[u8] = b"entity-id-11"; @@ -288,7 +291,7 @@ mod tests { payload: b"payload-v1".to_vec(), }; - sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) + sign_entity(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap(); @@ -300,7 +303,7 @@ mod tests { .await .unwrap(); - let err = verify_entity(&mut conn, &keyholder, &entity, ENTITY_ID) + let err = verify_entity(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap_err(); assert!(matches!(err, Error::MacMismatch { .. })); @@ -309,7 +312,7 @@ mod tests { #[tokio::test] async fn changed_payload_fails_verification() { let db = db::create_test_pool().await; - let keyholder = bootstrapped_keyholder(&db).await; + let vault = bootstrapped_vault(&db).await; let mut conn = db.get().await.unwrap(); const ENTITY_ID: &[u8] = b"entity-id-21"; @@ -319,7 +322,7 @@ mod tests { payload: b"payload-v1".to_vec(), }; - sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) + sign_entity(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap(); @@ -328,7 +331,7 @@ mod tests { ..entity }; - let err = verify_entity(&mut conn, &keyholder, &tampered, ENTITY_ID) + let err = verify_entity(&mut conn, &vault, &tampered, ENTITY_ID) .await .unwrap_err(); assert!(matches!(err, Error::MacMismatch { .. })); diff --git a/server/crates/arbiter-server/src/evm/mod.rs b/server/crates/arbiter-server/src/evm/mod.rs index 15ac999..0572e1f 100644 --- a/server/crates/arbiter-server/src/evm/mod.rs +++ b/server/crates/arbiter-server/src/evm/mod.rs @@ -11,7 +11,7 @@ use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::actor::ActorRef; use crate::{ - actors::keyholder::KeyHolder, + actors::vault::Vault, crypto::integrity, db::{ self, DatabaseError, @@ -138,7 +138,7 @@ async fn check_shared_constraints( // Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed pub struct Engine { db: db::DatabasePool, - keyholder: ActorRef, + vault: ActorRef, } impl Engine { @@ -158,7 +158,7 @@ impl Engine { .map_err(DatabaseError::from)? .ok_or(PolicyError::NoMatchingGrant)?; - integrity::verify_entity(&mut conn, &self.keyholder, &grant.settings, grant.id).await?; + integrity::verify_entity(&mut conn, &self.vault, &grant.settings, grant.id).await?; let mut violations = check_shared_constraints( &context, @@ -207,8 +207,8 @@ impl Engine { } impl Engine { - pub fn new(db: db::DatabasePool, keyholder: ActorRef) -> Self { - Self { db, keyholder } + pub fn new(db: db::DatabasePool, vault: ActorRef) -> Self { + Self { db, vault } } pub async fn create_grant( @@ -219,7 +219,7 @@ impl Engine { P::Settings: Clone, { let mut conn = self.db.get().await?; - let keyholder = self.keyholder.clone(); + let vault = self.vault.clone(); let id = conn .transaction(|conn| { @@ -258,7 +258,7 @@ impl Engine { P::create_grant(&basic_grant, &full_grant.specific, conn).await?; - integrity::sign_entity(conn, &keyholder, &full_grant, basic_grant.id) + integrity::sign_entity(conn, &vault, &full_grant, basic_grant.id) .await .map_err(|_| diesel::result::Error::RollbackTransaction)?; @@ -283,7 +283,7 @@ impl Engine { // Verify integrity of all grants before returning any results for grant in &all_grants { - integrity::verify_entity(conn, &self.keyholder, &grant.settings, grant.id).await?; + integrity::verify_entity(conn, &self.vault, &grant.settings, grant.id).await?; } Ok(all_grants.into_iter().map(|g| Grant { diff --git a/server/crates/arbiter-server/src/grpc/client.rs b/server/crates/arbiter-server/src/grpc/client.rs index 52fa1ea..3a98d3f 100644 --- a/server/crates/arbiter-server/src/grpc/client.rs +++ b/server/crates/arbiter-server/src/grpc/client.rs @@ -10,7 +10,7 @@ use tonic::Status; use tracing::{info, warn}; use crate::{ - actors::client::{ClientConnection, session::ClientSession}, + peers::client::{ClientConnection, session::ClientSession}, grpc::request_tracker::RequestTracker, }; diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 4a7b944..8ac14a5 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -22,7 +22,7 @@ use tonic::Status; use tracing::warn; use crate::{ - actors::client::{self, ClientConnection, auth}, + peers::client::{self, ClientConnection, auth}, grpc::request_tracker::RequestTracker, }; diff --git a/server/crates/arbiter-server/src/grpc/client/evm.rs b/server/crates/arbiter-server/src/grpc/client/evm.rs index 5b5ba2e..7d58448 100644 --- a/server/crates/arbiter-server/src/grpc/client/evm.rs +++ b/server/crates/arbiter-server/src/grpc/client/evm.rs @@ -16,7 +16,7 @@ use tonic::Status; use tracing::warn; use crate::{ - actors::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError}, + peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError}, grpc::{ Convert, TryConvert, common::inbound::{RawEvmAddress, RawEvmTransaction}, diff --git a/server/crates/arbiter-server/src/grpc/client/vault.rs b/server/crates/arbiter-server/src/grpc/client/vault.rs index b5e98e7..91fc44a 100644 --- a/server/crates/arbiter-server/src/grpc/client/vault.rs +++ b/server/crates/arbiter-server/src/grpc/client/vault.rs @@ -12,9 +12,9 @@ use kameo::{actor::ActorRef, error::SendError}; use tonic::Status; use tracing::warn; -use crate::actors::{ - client::session::{ClientSession, Error, HandleQueryVaultState}, - keyholder::KeyHolderState, +use crate::{ + peers::client::session::{ClientSession, Error, HandleQueryVaultState}, + actors::vault::VaultState, }; pub(super) async fn dispatch( @@ -30,9 +30,9 @@ pub(super) async fn dispatch( match payload { VaultRequestPayload::QueryState(_) => { let state = match actor.ask(HandleQueryVaultState {}).await { - Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, - Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed, - Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed, + Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, + Ok(VaultState::Sealed) => ProtoVaultState::Sealed, + Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed, Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error, Err(err) => { warn!(error = ?err, "Failed to query vault state"); diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs index 7181594..5bd2bf1 100644 --- a/server/crates/arbiter-server/src/grpc/mod.rs +++ b/server/crates/arbiter-server/src/grpc/mod.rs @@ -10,7 +10,7 @@ use tonic::{Request, Response, Status, async_trait}; use tracing::info; use crate::{ - actors::{client::ClientConnection, user_agent::UserAgentConnection}, + peers::{client::ClientConnection, user_agent::UserAgentConnection}, grpc::user_agent::start, }; diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index bc00687..a869c24 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -14,7 +14,7 @@ use tonic::Status; use tracing::{error, info, warn}; use crate::{ - actors::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, + peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, grpc::request_tracker::RequestTracker, }; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs index e2625e0..364408e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs @@ -18,7 +18,7 @@ use tonic::Status; use tracing::warn; use crate::{ - actors::user_agent::{UserAgentConnection, auth}, + peers::user_agent::{UserAgentConnection, auth}, grpc::request_tracker::RequestTracker, }; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs index 28725c2..a73880b 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs @@ -23,7 +23,7 @@ use tonic::Status; use tracing::warn; use crate::{ - actors::user_agent::{ + peers::user_agent::{ UserAgentSession, session::connection::{ GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, diff --git a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs index b0d832f..8d25818 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs @@ -21,7 +21,7 @@ use tonic::Status; use tracing::{info, warn}; use crate::{ - actors::user_agent::{ + peers::user_agent::{ OutOfBand, UserAgentSession, session::connection::{ HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove, diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault.rs index 8a2940f..f645c2f 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault.rs @@ -20,9 +20,9 @@ use kameo::{actor::ActorRef, error::SendError}; use tonic::Status; use tracing::warn; -use crate::actors::{ - keyholder::KeyHolderState, - user_agent::{ +use crate::{ + actors::vault::VaultState, + peers::user_agent::{ UserAgentSession, session::connection::{ BootstrapError, HandleBootstrapEncryptedKey, HandleQueryVaultState, @@ -166,9 +166,9 @@ async fn handle_query_vault_state( actor: &ActorRef, ) -> Result, Status> { let state = match actor.ask(HandleQueryVaultState {}).await { - Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, - Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed, - Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed, + Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, + Ok(VaultState::Sealed) => ProtoVaultState::Sealed, + Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed, Err(err) => { warn!(error = ?err, "Failed to query vault state"); ProtoVaultState::Error diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 8bb07c8..20444b1 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -2,6 +2,7 @@ use crate::context::ServerContext; pub mod actors; +pub mod peers; pub mod context; pub mod crypto; pub mod db; diff --git a/server/crates/arbiter-server/src/actors/client/auth.rs b/server/crates/arbiter-server/src/peers/client/auth.rs similarity index 94% rename from server/crates/arbiter-server/src/actors/client/auth.rs rename to server/crates/arbiter-server/src/peers/client/auth.rs index a9ff7c2..e21068e 100644 --- a/server/crates/arbiter-server/src/actors/client/auth.rs +++ b/server/crates/arbiter-server/src/peers/client/auth.rs @@ -14,9 +14,7 @@ use tracing::error; use crate::{ actors::{ - client::{ClientConnection, ClientCredentials, ClientProfile}, - flow_coordinator::{self, RequestClientApproval}, - keyholder::KeyHolder, + GlobalActors, flow_coordinator::{self, RequestClientApproval}, vault::Vault }, crypto::integrity::{self, AttestationStatus}, db::{ @@ -26,6 +24,8 @@ use crate::{ }, }; +use super::{ClientConnection, ClientCredentials, ClientProfile}; + #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] pub enum Error { #[error("Database pool unavailable")] @@ -104,7 +104,7 @@ async fn get_current_nonce_and_id( async fn verify_integrity( db: &db::DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, ) -> Result<(), Error> { let mut db_conn = db.get().await.map_err(|e| { @@ -119,7 +119,7 @@ async fn verify_integrity( let attestation = integrity::verify_entity( &mut db_conn, - keyholder, + vault, &ClientCredentials { pubkey: pubkey.clone(), nonce, @@ -144,7 +144,7 @@ async fn verify_integrity( /// Returns the new nonce, which is used as the challenge nonce. async fn create_nonce( db: &db::DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, ) -> Result { let pubkey_bytes = pubkey.to_bytes(); @@ -156,7 +156,7 @@ async fn create_nonce( })?; conn.exclusive_transaction(|conn| { - let keyholder = keyholder.clone(); + let vault = vault.clone(); let pubkey = pubkey.clone(); Box::pin(async move { let (id, new_nonce): (i32, i32) = update(program_client::table) @@ -168,7 +168,7 @@ async fn create_nonce( integrity::sign_entity( conn, - &keyholder, + &vault, &ClientCredentials { pubkey: pubkey.clone(), nonce: new_nonce, @@ -188,7 +188,7 @@ async fn create_nonce( } async fn approve_new_client( - actors: &crate::actors::GlobalActors, + actors: &GlobalActors, profile: ClientProfile, ) -> Result<(), Error> { let result = actors @@ -212,7 +212,7 @@ async fn approve_new_client( async fn insert_client( db: &db::DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, metadata: &ClientMetadata, ) -> Result { @@ -226,7 +226,7 @@ async fn insert_client( })?; conn.exclusive_transaction(|conn| { - let keyholder = keyholder.clone(); + let vault = vault.clone(); let pubkey = pubkey.clone(); Box::pin(async move { const NONCE_START: i32 = 1; @@ -254,7 +254,7 @@ async fn insert_client( integrity::sign_entity( conn, - &keyholder, + &vault, &ClientCredentials { pubkey: pubkey.clone(), nonce: NONCE_START, @@ -391,7 +391,7 @@ where let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? { Some((id, _)) => { - verify_integrity(&props.db, &props.actors.key_holder, &pubkey).await?; + verify_integrity(&props.db, &props.actors.vault, &pubkey).await?; id } None => { @@ -403,12 +403,12 @@ where }, ) .await?; - insert_client(&props.db, &props.actors.key_holder, &pubkey, &metadata).await? + insert_client(&props.db, &props.actors.vault, &pubkey, &metadata).await? } }; sync_client_metadata(&props.db, client_id, &metadata).await?; - let challenge_nonce = create_nonce(&props.db, &props.actors.key_holder, &pubkey).await?; + let challenge_nonce = create_nonce(&props.db, &props.actors.vault, &pubkey).await?; challenge_client(transport, pubkey, challenge_nonce).await?; transport diff --git a/server/crates/arbiter-server/src/actors/client/mod.rs b/server/crates/arbiter-server/src/peers/client/mod.rs similarity index 95% rename from server/crates/arbiter-server/src/actors/client/mod.rs rename to server/crates/arbiter-server/src/peers/client/mod.rs index 03b8861..b058b94 100644 --- a/server/crates/arbiter-server/src/actors/client/mod.rs +++ b/server/crates/arbiter-server/src/peers/client/mod.rs @@ -4,9 +4,9 @@ use kameo::actor::Spawn; use tracing::{error, info}; use crate::{ - actors::{GlobalActors, client::session::ClientSession}, + actors::GlobalActors, crypto::integrity::{Integrable, hashing::Hashable}, - db, + db, peers::client::session::ClientSession, }; #[derive(Debug, Clone)] diff --git a/server/crates/arbiter-server/src/actors/client/session.rs b/server/crates/arbiter-server/src/peers/client/session.rs similarity index 89% rename from server/crates/arbiter-server/src/actors/client/session.rs rename to server/crates/arbiter-server/src/peers/client/session.rs index 184c650..d09d844 100644 --- a/server/crates/arbiter-server/src/actors/client/session.rs +++ b/server/crates/arbiter-server/src/peers/client/session.rs @@ -6,15 +6,16 @@ use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature}; use crate::{ actors::{ GlobalActors, - client::ClientConnection, evm::{ClientSignTransaction, SignTransactionError}, flow_coordinator::RegisterClient, - keyholder::KeyHolderState, + vault::VaultState, }, db, evm::VetError, }; +use super::ClientConnection; + pub struct ClientSession { props: ClientConnection, client_id: i32, @@ -29,13 +30,13 @@ impl ClientSession { #[messages] impl ClientSession { #[message] - pub(crate) async fn handle_query_vault_state(&mut self) -> Result { - use crate::actors::keyholder::GetState; + pub(crate) async fn handle_query_vault_state(&mut self) -> Result { + use crate::actors::vault::GetState; - let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { + let vault_state = match self.props.actors.vault.ask(GetState {}).await { Ok(state) => state, Err(err) => { - error!(?err, actor = "client", "keyholder.query.failed"); + error!(?err, actor = "client", "vault.query.failed"); return Err(Error::Internal); } }; diff --git a/server/crates/arbiter-server/src/peers/mod.rs b/server/crates/arbiter-server/src/peers/mod.rs new file mode 100644 index 0000000..c4d3e03 --- /dev/null +++ b/server/crates/arbiter-server/src/peers/mod.rs @@ -0,0 +1,2 @@ +pub mod user_agent; +pub mod client; \ No newline at end of file diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth.rs b/server/crates/arbiter-server/src/peers/user_agent/auth.rs similarity index 96% rename from server/crates/arbiter-server/src/actors/user_agent/auth.rs rename to server/crates/arbiter-server/src/peers/user_agent/auth.rs index 00d2d55..2972d0c 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth.rs @@ -2,13 +2,11 @@ use arbiter_crypto::authn; use arbiter_proto::transport::Bi; use tracing::error; -use crate::actors::user_agent::{ - UserAgentConnection, - auth::state::{AuthContext, AuthStateMachine}, -}; mod state; use state::*; +use super::UserAgentConnection; + #[derive(Debug, Clone)] pub enum Inbound { AuthChallengeRequest { diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs similarity index 94% rename from server/crates/arbiter-server/src/actors/user_agent/auth/state.rs rename to server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index 60bcf6f..8a3abe6 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -4,13 +4,14 @@ use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update}; use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::actor::ActorRef; use tracing::error; +use super::super::{UserAgentCredentials, UserAgentConnection}; use super::Error; +use crate::peers::user_agent::auth::Outbound; use crate::{ actors::{ bootstrap::ConsumeToken, - keyholder::KeyHolder, - user_agent::{UserAgentConnection, UserAgentCredentials, auth::Outbound}, + vault::Vault, }, crypto::integrity, db::{DatabasePool, schema::useragent_client}, @@ -77,7 +78,7 @@ async fn get_current_nonce_and_id( async fn verify_integrity( db: &DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, ) -> Result<(), Error> { let mut db_conn = db.get().await.map_err(|e| { @@ -89,7 +90,7 @@ async fn verify_integrity( let _result = integrity::verify_entity( &mut db_conn, - keyholder, + vault, &UserAgentCredentials { pubkey: pubkey.clone(), nonce, @@ -107,7 +108,7 @@ async fn verify_integrity( async fn create_nonce( db: &DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, ) -> Result { let mut db_conn = db.get().await.map_err(|e| { @@ -130,7 +131,7 @@ async fn create_nonce( integrity::sign_entity( conn, - keyholder, + vault, &UserAgentCredentials { pubkey: pubkey.clone(), nonce: new_nonce, @@ -152,7 +153,7 @@ async fn create_nonce( async fn register_key( db: &DatabasePool, - keyholder: &ActorRef, + vault: &ActorRef, pubkey: &authn::PublicKey, ) -> Result<(), Error> { let pubkey_bytes = pubkey.to_bytes(); @@ -183,7 +184,7 @@ async fn register_key( nonce: NONCE_START, }; - integrity::sign_entity(conn, keyholder, &entity, id) + integrity::sign_entity(conn, vault, &entity, id) .await .map_err(|e| { error!(error = ?e, "Failed to sign integrity tag for new user-agent key"); @@ -219,9 +220,9 @@ where &mut self, ChallengeRequest { pubkey }: ChallengeRequest, ) -> Result { - verify_integrity(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; + verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; - let nonce = create_nonce(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; + let nonce = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; self.transport .send(Ok(Outbound::AuthChallenge { nonce })) @@ -263,7 +264,7 @@ where match token_ok { true => { - register_key(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; + register_key(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; self.transport .send(Ok(Outbound::AuthSuccess)) .await diff --git a/server/crates/arbiter-server/src/actors/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/mod.rs similarity index 95% rename from server/crates/arbiter-server/src/actors/user_agent/mod.rs rename to server/crates/arbiter-server/src/peers/user_agent/mod.rs index ac571d9..83fdbbb 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/mod.rs @@ -1,7 +1,7 @@ use crate::{ - actors::{GlobalActors, client::ClientProfile}, + actors::GlobalActors, crypto::integrity::Integrable, - db, + db, peers::client::ClientProfile, }; use arbiter_crypto::authn; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/peers/user_agent/session.rs similarity index 96% rename from server/crates/arbiter-server/src/actors/user_agent/session.rs rename to server/crates/arbiter-server/src/peers/user_agent/session.rs index d3410bd..23a2fb1 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session.rs @@ -8,14 +8,12 @@ use kameo::{Actor, actor::ActorRef, messages}; use thiserror::Error; use tracing::error; -use crate::actors::{ - client::ClientProfile, - flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, - user_agent::{OutOfBand, UserAgentConnection}, -}; +use crate::{actors::flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, peers::client::ClientProfile}; mod state; use state::{DummyContext, UserAgentEvents, UserAgentStateMachine}; +use super::{OutOfBand, UserAgentConnection}; + #[derive(Debug, Error)] pub enum Error { #[error("State transition failed")] diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs similarity index 93% rename from server/crates/arbiter-server/src/actors/user_agent/session/connection.rs rename to server/crates/arbiter-server/src/peers/user_agent/session/connection.rs index 71f4067..119800f 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs @@ -14,28 +14,27 @@ use kameo::prelude::Context; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; -use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer; -use crate::actors::keyholder::KeyHolderState; -use crate::actors::user_agent::session::Error; +use crate::{actors::vault::VaultState, peers::user_agent::session::state::{UnsealContext, UserAgentEvents}}; use crate::actors::{ evm::{ ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, UseragentCreateGrant, UseragentListGrants, }, - keyholder::{self, Bootstrap, TryUnseal}, - user_agent::session::{ - UserAgentSession, - state::{UnsealContext, UserAgentEvents, UserAgentStates}, - }, + vault::{self, Bootstrap, TryUnseal}, }; use crate::db::models::{ EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata, }; use crate::evm::policies::{Grant, SpecificGrant}; +use crate::{ + actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer, +}; + +use super::{UserAgentSession, state, Error}; impl UserAgentSession { fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> { - let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { + let state::UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { error!("Received encrypted key in invalid state"); return Err(Error::internal("Invalid state for unseal encrypted key")); }; @@ -184,7 +183,7 @@ impl UserAgentSession { match self .props .actors - .key_holder + .vault .ask(TryUnseal { seal_key_raw: seal_key_buffer, }) @@ -195,17 +194,17 @@ impl UserAgentSession { self.transition(UserAgentEvents::ReceivedValidKey)?; Ok(()) } - Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { + Err(SendError::HandlerError(vault::Error::InvalidKey)) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(UnsealError::InvalidKey) } Err(SendError::HandlerError(err)) => { - error!(?err, "Keyholder failed to unseal key"); + error!(?err, "Vault failed to unseal key"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(UnsealError::InvalidKey) } Err(err) => { - error!(?err, "Failed to send unseal request to keyholder"); + error!(?err, "Failed to send unseal request to vault"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(Error::internal("Vault actor error").into()) } @@ -245,7 +244,7 @@ impl UserAgentSession { match self .props .actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: seal_key_buffer, }) @@ -256,17 +255,17 @@ impl UserAgentSession { self.transition(UserAgentEvents::ReceivedValidKey)?; Ok(()) } - Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { + Err(SendError::HandlerError(vault::Error::AlreadyBootstrapped)) => { self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(BootstrapError::AlreadyBootstrapped) } Err(SendError::HandlerError(err)) => { - error!(?err, "Keyholder failed to bootstrap vault"); + error!(?err, "Vault failed to bootstrap vault"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(BootstrapError::InvalidKey) } Err(err) => { - error!(?err, "Failed to send bootstrap request to keyholder"); + error!(?err, "Failed to send bootstrap request to vault"); self.transition(UserAgentEvents::ReceivedInvalidKey)?; Err(BootstrapError::General(Error::internal( "Vault actor error", @@ -279,13 +278,13 @@ impl UserAgentSession { #[messages] impl UserAgentSession { #[message] - pub(crate) async fn handle_query_vault_state(&mut self) -> Result { - use crate::actors::keyholder::GetState; + pub(crate) async fn handle_query_vault_state(&mut self) -> Result { + use crate::actors::vault::GetState; - let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { + let vault_state = match self.props.actors.vault.ask(GetState {}).await { Ok(state) => state, Err(err) => { - error!(?err, actor = "useragent", "keyholder.query.failed"); + error!(?err, actor = "useragent", "vault.query.failed"); return Err(Error::internal("Vault is in broken state")); } }; @@ -374,7 +373,7 @@ impl UserAgentSession { // Err(GrantMutationError::Internal) // } // } - let _ = grant_id; + let _ = grant_id; todo!() } diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/state.rs b/server/crates/arbiter-server/src/peers/user_agent/session/state.rs similarity index 100% rename from server/crates/arbiter-server/src/actors/user_agent/session/state.rs rename to server/crates/arbiter-server/src/peers/user_agent/session/state.rs diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index a7137e3..80e2652 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -7,9 +7,10 @@ use arbiter_proto::transport::{Receiver, Sender}; use arbiter_server::{ actors::{ GlobalActors, - client::{ClientConnection, ClientCredentials, auth, connect_client}, - keyholder::Bootstrap, + + vault::Bootstrap, }, + peers::client::{ClientConnection, ClientCredentials, auth, connect_client}, crypto::integrity, db::{self, schema}, }; @@ -58,7 +59,7 @@ async fn insert_registered_client( integrity::sign_entity( &mut conn, - &actors.key_holder, + &actors.vault, &ClientCredentials { pubkey: pubkey.into(), nonce: 1, @@ -103,7 +104,7 @@ async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors { let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), }) diff --git a/server/crates/arbiter-server/tests/common/mod.rs b/server/crates/arbiter-server/tests/common/mod.rs index c4e6878..17676d4 100644 --- a/server/crates/arbiter-server/tests/common/mod.rs +++ b/server/crates/arbiter-server/tests/common/mod.rs @@ -1,7 +1,7 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_proto::transport::{Bi, Error, Receiver, Sender}; use arbiter_server::{ - actors::keyholder::KeyHolder, + actors::{GlobalActors, vault::Vault}, db::{self, schema}, }; @@ -11,8 +11,8 @@ use diesel_async::RunQueryDsl; use tokio::sync::mpsc; #[allow(dead_code)] -pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder { - let mut actor = KeyHolder::new(db.clone()).await.unwrap(); +pub async fn bootstrapped_vault(db: &db::DatabasePool) -> Vault { + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); actor .bootstrap(SafeCell::new(b"test-seal-key".to_vec())) .await diff --git a/server/crates/arbiter-server/tests/keyholder.rs b/server/crates/arbiter-server/tests/keyholder.rs deleted file mode 100644 index 0fa5692..0000000 --- a/server/crates/arbiter-server/tests/keyholder.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod common; - -#[path = "keyholder/concurrency.rs"] -mod concurrency; -#[path = "keyholder/lifecycle.rs"] -mod lifecycle; -#[path = "keyholder/storage.rs"] -mod storage; diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index aeccc8a..31d6786 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -8,9 +8,10 @@ use arbiter_server::{ actors::{ GlobalActors, bootstrap::GetToken, - keyholder::Bootstrap, - user_agent::{UserAgentConnection, UserAgentCredentials, auth}, + vault::Bootstrap, + }, + peers::user_agent::{UserAgentConnection, UserAgentCredentials, auth}, crypto::integrity, db::{self, schema}, }; @@ -38,7 +39,7 @@ pub async fn test_bootstrap_token_auth() { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), }) @@ -124,7 +125,7 @@ pub async fn test_challenge_auth() { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), }) @@ -147,7 +148,7 @@ pub async fn test_challenge_auth() { .unwrap(); integrity::sign_entity( &mut conn, - &actors.key_holder, + &actors.vault, &UserAgentCredentials { pubkey: new_key.verifying_key().into(), nonce: 1, @@ -213,7 +214,7 @@ pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), }) @@ -262,7 +263,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), }) @@ -285,7 +286,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { .unwrap(); integrity::sign_entity( &mut conn, - &actors.key_holder, + &actors.vault, &UserAgentCredentials { pubkey: new_key.verifying_key().into(), nonce: 1, diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index 15cf475..59ef627 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -2,12 +2,13 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ actors::{ GlobalActors, - keyholder::{Bootstrap, Seal}, - user_agent::{ + vault::{Bootstrap, Seal}, + + }, + peers::user_agent::{ UserAgentSession, session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, }, - }, db, }; @@ -22,13 +23,13 @@ async fn setup_sealed_user_agent( let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors - .key_holder + .vault .ask(Bootstrap { seal_key_raw: SafeCell::new(seal_key.to_vec()), }) .await .unwrap(); - actors.key_holder.ask(Seal).await.unwrap(); + actors.vault.ask(Seal).await.unwrap(); let session = UserAgentSession::spawn(UserAgentSession::new_test(db.clone(), actors)); diff --git a/server/crates/arbiter-server/tests/vault.rs b/server/crates/arbiter-server/tests/vault.rs new file mode 100644 index 0000000..c7640a8 --- /dev/null +++ b/server/crates/arbiter-server/tests/vault.rs @@ -0,0 +1,8 @@ +mod common; + +#[path = "vault/concurrency.rs"] +mod concurrency; +#[path = "vault/lifecycle.rs"] +mod lifecycle; +#[path = "vault/storage.rs"] +mod storage; diff --git a/server/crates/arbiter-server/tests/keyholder/concurrency.rs b/server/crates/arbiter-server/tests/vault/concurrency.rs similarity index 91% rename from server/crates/arbiter-server/tests/keyholder/concurrency.rs rename to server/crates/arbiter-server/tests/vault/concurrency.rs index f128beb..777b203 100644 --- a/server/crates/arbiter-server/tests/keyholder/concurrency.rs +++ b/server/crates/arbiter-server/tests/vault/concurrency.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ - actors::keyholder::{CreateNew, Error, KeyHolder}, + actors::{GlobalActors, vault::{CreateNew, Error, Vault}}, db::{self, models, schema}, }; @@ -14,7 +14,7 @@ use tokio::task::JoinSet; use crate::common; async fn write_concurrently( - actor: ActorRef, + actor: ActorRef, prefix: &'static str, count: usize, ) -> Vec<(i32, Vec)> { @@ -44,7 +44,7 @@ async fn write_concurrently( #[test_log::test] async fn concurrent_create_new_no_duplicate_nonces_() { let db = db::create_test_pool().await; - let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); + let actor = Vault::spawn(common::bootstrapped_vault(&db).await); let writes = write_concurrently(actor, "nonce-unique", 32).await; assert_eq!(writes.len(), 32); @@ -66,7 +66,7 @@ async fn concurrent_create_new_no_duplicate_nonces_() { #[test_log::test] async fn concurrent_create_new_root_nonce_never_moves_backward() { let db = db::create_test_pool().await; - let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); + let actor = Vault::spawn(common::bootstrapped_vault(&db).await); write_concurrently(actor, "root-max", 24).await; @@ -94,7 +94,7 @@ async fn concurrent_create_new_root_nonce_never_moves_backward() { #[test_log::test] async fn insert_failure_does_not_create_partial_row() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let root_key_history_id = common::root_key_history_id(&db).await; let mut conn = db.get().await.unwrap(); @@ -156,12 +156,12 @@ async fn insert_failure_does_not_create_partial_row() { #[test_log::test] async fn decrypt_roundtrip_after_high_concurrency() { let db = db::create_test_pool().await; - let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); + let actor = Vault::spawn(common::bootstrapped_vault(&db).await); let writes = write_concurrently(actor, "roundtrip", 40).await; let expected: HashMap> = writes.into_iter().collect(); - let mut decryptor = KeyHolder::new(db.clone()).await.unwrap(); + let mut decryptor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); decryptor .try_unseal(SafeCell::new(b"test-seal-key".to_vec())) .await diff --git a/server/crates/arbiter-server/tests/keyholder/lifecycle.rs b/server/crates/arbiter-server/tests/vault/lifecycle.rs similarity index 76% rename from server/crates/arbiter-server/tests/keyholder/lifecycle.rs rename to server/crates/arbiter-server/tests/vault/lifecycle.rs index bd50b6f..95cc52f 100644 --- a/server/crates/arbiter-server/tests/keyholder/lifecycle.rs +++ b/server/crates/arbiter-server/tests/vault/lifecycle.rs @@ -1,8 +1,12 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ - actors::keyholder::{Error, KeyHolder}, + actors::{GlobalActors, vault::{Error, Vault}}, crypto::encryption::v1::{Nonce, ROOT_KEY_TAG}, db::{self, models, schema}, + peers::user_agent::{ + UserAgentSession, + session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, + }, }; use diesel::{QueryDsl, SelectableHelper}; @@ -14,7 +18,7 @@ use crate::common; #[test_log::test] async fn test_bootstrap() { let db = db::create_test_pool().await; - let mut actor = KeyHolder::new(db.clone()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.bootstrap(seal_key).await.unwrap(); @@ -37,7 +41,7 @@ async fn test_bootstrap() { #[test_log::test] async fn test_bootstrap_rejects_double() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let seal_key2 = SafeCell::new(b"test-seal-key".to_vec()); let err = actor.bootstrap(seal_key2).await.unwrap_err(); @@ -48,7 +52,7 @@ async fn test_bootstrap_rejects_double() { #[test_log::test] async fn test_create_new_before_bootstrap_fails() { let db = db::create_test_pool().await; - let mut actor = KeyHolder::new(db).await.unwrap(); + let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); let err = actor .create_new(SafeCell::new(b"data".to_vec())) @@ -61,7 +65,7 @@ async fn test_create_new_before_bootstrap_fails() { #[test_log::test] async fn test_decrypt_before_bootstrap_fails() { let db = db::create_test_pool().await; - let mut actor = KeyHolder::new(db).await.unwrap(); + let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); let err = actor.decrypt(1).await.unwrap_err(); assert!(matches!(err, Error::NotBootstrapped)); @@ -71,10 +75,10 @@ async fn test_decrypt_before_bootstrap_fails() { #[test_log::test] async fn test_new_restores_sealed_state() { let db = db::create_test_pool().await; - let actor = common::bootstrapped_keyholder(&db).await; + let actor = common::bootstrapped_vault(&db).await; drop(actor); - let mut actor2 = KeyHolder::new(db).await.unwrap(); + let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); let err = actor2.decrypt(1).await.unwrap_err(); assert!(matches!(err, Error::NotBootstrapped)); } @@ -83,7 +87,7 @@ async fn test_new_restores_sealed_state() { #[test_log::test] async fn test_unseal_correct_password() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let plaintext = b"survive a restart"; let aead_id = actor @@ -92,7 +96,7 @@ async fn test_unseal_correct_password() { .unwrap(); drop(actor); - let mut actor = KeyHolder::new(db.clone()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.try_unseal(seal_key).await.unwrap(); @@ -104,7 +108,7 @@ async fn test_unseal_correct_password() { #[test_log::test] async fn test_unseal_wrong_then_correct_password() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let plaintext = b"important data"; let aead_id = actor @@ -113,7 +117,7 @@ async fn test_unseal_wrong_then_correct_password() { .unwrap(); drop(actor); - let mut actor = KeyHolder::new(db.clone()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); let bad_key = SafeCell::new(b"wrong-password".to_vec()); let err = actor.try_unseal(bad_key).await.unwrap_err(); diff --git a/server/crates/arbiter-server/tests/keyholder/storage.rs b/server/crates/arbiter-server/tests/vault/storage.rs similarity index 91% rename from server/crates/arbiter-server/tests/keyholder/storage.rs rename to server/crates/arbiter-server/tests/vault/storage.rs index 71ebccf..ae6d3d3 100644 --- a/server/crates/arbiter-server/tests/keyholder/storage.rs +++ b/server/crates/arbiter-server/tests/vault/storage.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ - actors::keyholder::Error, + actors::vault::Error, crypto::encryption::v1::Nonce, db::{self, models, schema}, }; @@ -16,7 +16,7 @@ use crate::common; #[test_log::test] async fn test_create_decrypt_roundtrip() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let plaintext = b"hello arbiter"; let aead_id = actor @@ -32,7 +32,7 @@ async fn test_create_decrypt_roundtrip() { #[test_log::test] async fn test_decrypt_nonexistent_returns_not_found() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let err = actor.decrypt(9999).await.unwrap_err(); assert!(matches!(err, Error::NotFound)); @@ -42,7 +42,7 @@ async fn test_decrypt_nonexistent_returns_not_found() { #[test_log::test] async fn test_ciphertext_differs_across_entries() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let plaintext = b"same content"; let id1 = actor @@ -80,7 +80,7 @@ async fn test_ciphertext_differs_across_entries() { #[test_log::test] async fn test_nonce_never_reused() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let n = 5; for i in 0..n { @@ -124,7 +124,7 @@ async fn test_nonce_never_reused() { #[test_log::test] async fn broken_db_nonce_format_fails_closed() { let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let root_key_history_id = common::root_key_history_id(&db).await; let mut conn = db.get().await.unwrap(); @@ -145,7 +145,7 @@ async fn broken_db_nonce_format_fails_closed() { assert!(matches!(err, Error::BrokenDatabase)); let db = db::create_test_pool().await; - let mut actor = common::bootstrapped_keyholder(&db).await; + let mut actor = common::bootstrapped_vault(&db).await; let id = actor .create_new(SafeCell::new(b"decrypt target".to_vec())) .await From 6b8da567dd25bd920cff65de2590169769f51dd9 Mon Sep 17 00:00:00 2001 From: hdbg Date: Wed, 8 Apr 2026 12:34:32 +0200 Subject: [PATCH 02/13] fix(server::user_agent): useragents now self-sign themselves on bootstrap --- protobufs/user_agent/auth.proto | 8 -- .../client_connect_approval.rs | 5 +- .../src/actors/flow_coordinator/mod.rs | 8 +- .../crates/arbiter-server/src/actors/mod.rs | 3 +- .../arbiter-server/src/actors/vault/mod.rs | 2 +- .../arbiter-server/src/crypto/integrity/v1.rs | 18 ++- .../crates/arbiter-server/src/grpc/client.rs | 2 +- .../arbiter-server/src/grpc/client/auth.rs | 2 +- .../arbiter-server/src/grpc/client/evm.rs | 2 +- .../arbiter-server/src/grpc/client/vault.rs | 2 +- server/crates/arbiter-server/src/grpc/mod.rs | 2 +- .../arbiter-server/src/grpc/user_agent.rs | 14 ++- .../src/grpc/user_agent/auth.rs | 5 +- .../arbiter-server/src/grpc/user_agent/evm.rs | 8 +- .../src/grpc/user_agent/sdk_client.rs | 4 +- server/crates/arbiter-server/src/lib.rs | 2 +- .../arbiter-server/src/peers/client/auth.rs | 9 +- .../arbiter-server/src/peers/client/mod.rs | 3 +- server/crates/arbiter-server/src/peers/mod.rs | 2 +- .../src/peers/user_agent/auth.rs | 4 +- .../src/peers/user_agent/auth/state.rs | 92 ++++++--------- .../src/peers/user_agent/mod.rs | 4 +- .../src/peers/user_agent/session.rs | 109 +++++++++++++++--- .../peers/user_agent/session/connection.rs | 7 +- .../arbiter-server/tests/client/auth.rs | 8 +- .../crates/arbiter-server/tests/common/mod.rs | 4 +- .../arbiter-server/tests/user_agent/auth.rs | 9 +- .../arbiter-server/tests/user_agent/unseal.rs | 9 +- .../arbiter-server/tests/vault/concurrency.rs | 9 +- .../arbiter-server/tests/vault/lifecycle.rs | 33 ++++-- useragent/lib/features/connection/auth.dart | 5 - useragent/lib/proto/shared/evm.pb.dart | 96 ++++++++++++++- useragent/lib/proto/shared/evm.pbjson.dart | 24 +++- useragent/lib/proto/user_agent/auth.pb.dart | 13 --- .../lib/proto/user_agent/auth.pbenum.dart | 25 ---- .../lib/proto/user_agent/auth.pbjson.dart | 29 +---- 36 files changed, 352 insertions(+), 229 deletions(-) diff --git a/protobufs/user_agent/auth.proto b/protobufs/user_agent/auth.proto index d2c5528..989e339 100644 --- a/protobufs/user_agent/auth.proto +++ b/protobufs/user_agent/auth.proto @@ -2,17 +2,9 @@ syntax = "proto3"; package arbiter.user_agent.auth; -enum KeyType { - KEY_TYPE_UNSPECIFIED = 0; - KEY_TYPE_ED25519 = 1; - KEY_TYPE_ECDSA_SECP256K1 = 2; - KEY_TYPE_RSA = 3; -} - message AuthChallengeRequest { bytes pubkey = 1; optional string bootstrap_token = 2; - KeyType key_type = 3; } message AuthChallenge { diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs index 293a463..4868c72 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs @@ -8,7 +8,10 @@ use kameo::{ use crate::{ actors::flow_coordinator::ApprovalError, - peers::{client::ClientProfile, user_agent::{UserAgentSession, session::BeginNewClientApproval}}, + peers::{ + client::ClientProfile, + user_agent::{UserAgentSession, session::BeginNewClientApproval}, + }, }; pub struct Args { diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs index 7261f7f..51a9bac 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs @@ -9,7 +9,13 @@ use kameo::{ }; use tracing::info; -use crate::{actors::flow_coordinator::client_connect_approval::ClientApprovalController, peers::{client::{ClientProfile, session::ClientSession}, user_agent::UserAgentSession}}; +use crate::{ + actors::flow_coordinator::client_connect_approval::ClientApprovalController, + peers::{ + client::{ClientProfile, session::ClientSession}, + user_agent::UserAgentSession, + }, +}; pub mod client_connect_approval; diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index 155d34d..8b2ebd8 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -4,8 +4,7 @@ use thiserror::Error; use crate::{ actors::{ - bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, - vault::Vault, + bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, vault::Vault, }, db, }; diff --git a/server/crates/arbiter-server/src/actors/vault/mod.rs b/server/crates/arbiter-server/src/actors/vault/mod.rs index 7a4f456..0214813 100644 --- a/server/crates/arbiter-server/src/actors/vault/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault/mod.rs @@ -147,7 +147,7 @@ impl Vault { Ok(nonce) } - fn expect_unsealed<'a>(state: &'a mut State) -> Result<&'a mut Unsealed, Error> { + fn expect_unsealed(state: &mut State) -> Result<&mut Unsealed, Error> { match state { State::Unsealed(unsealed) => Ok(unsealed), State::Unbootstrapped => Err(Error::NotBootstrapped), diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index 87199d3..21efbe7 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -1,6 +1,8 @@ -use crate::{actors::vault, crypto::integrity::hashing::Hashable}; -use arbiter_crypto::safecell::SafeCellHandle as _; -use hmac::{Hmac, Mac as _}; +use crate::{ + actors::vault::{self, GetState}, + crypto::integrity::hashing::Hashable, +}; +use hmac::Hmac; use sha2::Sha256; use diesel::{ExpressionMethods as _, QueryDsl, dsl::insert_into, sqlite::Sqlite}; @@ -199,6 +201,11 @@ pub async fn verify_entity( } } +pub async fn is_signing_available(vault: &ActorRef) -> Result { + let state = vault.ask(GetState).await.map_err(|_| Error::VaultSend)?; + Ok(matches!(state, vault::VaultState::Unsealed)) +} + #[cfg(test)] mod tests { use diesel::{ExpressionMethods as _, QueryDsl}; @@ -208,7 +215,10 @@ mod tests { use sha2::Digest; use crate::{ - actors::{GlobalActors, vault::{Bootstrap, Vault}}, + actors::{ + GlobalActors, + vault::{Bootstrap, Vault}, + }, db::{self, schema}, }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; diff --git a/server/crates/arbiter-server/src/grpc/client.rs b/server/crates/arbiter-server/src/grpc/client.rs index 3a98d3f..6fd8223 100644 --- a/server/crates/arbiter-server/src/grpc/client.rs +++ b/server/crates/arbiter-server/src/grpc/client.rs @@ -10,8 +10,8 @@ use tonic::Status; use tracing::{info, warn}; use crate::{ - peers::client::{ClientConnection, session::ClientSession}, grpc::request_tracker::RequestTracker, + peers::client::{ClientConnection, session::ClientSession}, }; mod auth; diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 8ac14a5..000a9db 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -22,8 +22,8 @@ use tonic::Status; use tracing::warn; use crate::{ - peers::client::{self, ClientConnection, auth}, grpc::request_tracker::RequestTracker, + peers::client::{self, ClientConnection, auth}, }; pub struct AuthTransportAdapter<'a> { diff --git a/server/crates/arbiter-server/src/grpc/client/evm.rs b/server/crates/arbiter-server/src/grpc/client/evm.rs index 7d58448..2f621a9 100644 --- a/server/crates/arbiter-server/src/grpc/client/evm.rs +++ b/server/crates/arbiter-server/src/grpc/client/evm.rs @@ -16,11 +16,11 @@ use tonic::Status; use tracing::warn; use crate::{ - peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError}, grpc::{ Convert, TryConvert, common::inbound::{RawEvmAddress, RawEvmTransaction}, }, + peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError}, }; fn wrap_response(payload: EvmResponsePayload) -> ClientResponsePayload { diff --git a/server/crates/arbiter-server/src/grpc/client/vault.rs b/server/crates/arbiter-server/src/grpc/client/vault.rs index 91fc44a..fd33388 100644 --- a/server/crates/arbiter-server/src/grpc/client/vault.rs +++ b/server/crates/arbiter-server/src/grpc/client/vault.rs @@ -13,8 +13,8 @@ use tonic::Status; use tracing::warn; use crate::{ - peers::client::session::{ClientSession, Error, HandleQueryVaultState}, actors::vault::VaultState, + peers::client::session::{ClientSession, Error, HandleQueryVaultState}, }; pub(super) async fn dispatch( diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs index 5bd2bf1..cc93dc6 100644 --- a/server/crates/arbiter-server/src/grpc/mod.rs +++ b/server/crates/arbiter-server/src/grpc/mod.rs @@ -10,8 +10,8 @@ use tonic::{Request, Response, Status, async_trait}; use tracing::info; use crate::{ - peers::{client::ClientConnection, user_agent::UserAgentConnection}, grpc::user_agent::start, + peers::{client::ClientConnection, user_agent::UserAgentConnection}, }; mod request_tracker; diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index a869c24..b94e7ab 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -14,8 +14,8 @@ use tonic::Status; use tracing::{error, info, warn}; use crate::{ - peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, grpc::request_tracker::RequestTracker, + peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, }; mod auth; @@ -124,7 +124,7 @@ pub async fn start( ) { let mut request_tracker = RequestTracker::default(); - let pubkey = match auth::start(&mut conn, &mut bi, &mut request_tracker).await { + let (id, pubkey) = match auth::start(&mut conn, &mut bi, &mut request_tracker).await { Ok(pubkey) => pubkey, Err(e) => { warn!(error = ?e, "Authentication failed"); @@ -132,13 +132,19 @@ pub async fn start( } }; + info!(?pubkey, "User authenticated successfully"); + let (oob_sender, oob_receiver) = mpsc::channel(16); let oob_adapter = OutOfBandAdapter(oob_sender); - let actor = UserAgentSession::spawn(UserAgentSession::new(conn, Box::new(oob_adapter))); + let actor = UserAgentSession::spawn(UserAgentSession::new( + conn, + id, + pubkey, + Box::new(oob_adapter), + )); let actor_for_cleanup = actor.clone(); - info!(?pubkey, "User authenticated successfully"); dispatch_loop(bi, actor, oob_receiver, request_tracker).await; actor_for_cleanup.kill(); } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs index 364408e..acb65fc 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs @@ -18,8 +18,8 @@ use tonic::Status; use tracing::warn; use crate::{ - peers::user_agent::{UserAgentConnection, auth}, grpc::request_tracker::RequestTracker, + peers::user_agent::{UserAgentConnection, auth}, }; pub struct AuthTransportAdapter<'a> { @@ -140,7 +140,6 @@ impl Receiver for AuthTransportAdapter<'_> { AuthRequestPayload::ChallengeRequest(ProtoAuthChallengeRequest { pubkey, bootstrap_token, - key_type: _, }) => { let Ok(pubkey) = authn::PublicKey::try_from(pubkey.as_slice()) else { warn!( @@ -168,7 +167,7 @@ pub async fn start( conn: &mut UserAgentConnection, bi: &mut GrpcBi, request_tracker: &mut RequestTracker, -) -> Result { +) -> Result<(i32, authn::PublicKey), auth::Error> { let transport = AuthTransportAdapter::new(bi, request_tracker); auth::authenticate(conn, transport).await } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs index a73880b..386e7c5 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs @@ -23,6 +23,10 @@ use tonic::Status; use tracing::warn; use crate::{ + grpc::{ + Convert, TryConvert, + common::inbound::{RawEvmAddress, RawEvmTransaction}, + }, peers::user_agent::{ UserAgentSession, session::connection::{ @@ -31,10 +35,6 @@ use crate::{ SignTransactionError as SessionSignTransactionError, }, }, - grpc::{ - Convert, TryConvert, - common::inbound::{RawEvmAddress, RawEvmTransaction}, - }, }; fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload { diff --git a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs index 8d25818..fbf025f 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs @@ -21,6 +21,8 @@ use tonic::Status; use tracing::{info, warn}; use crate::{ + db::models::NewEvmWalletAccess, + grpc::Convert, peers::user_agent::{ OutOfBand, UserAgentSession, session::connection::{ @@ -28,8 +30,6 @@ use crate::{ HandleRevokeEvmWalletAccess, HandleSdkClientList, }, }, - db::models::NewEvmWalletAccess, - grpc::Convert, }; fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> UserAgentResponsePayload { diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 20444b1..466e639 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -2,12 +2,12 @@ use crate::context::ServerContext; pub mod actors; -pub mod peers; pub mod context; pub mod crypto; pub mod db; pub mod evm; pub mod grpc; +pub mod peers; pub mod utils; pub struct Server { diff --git a/server/crates/arbiter-server/src/peers/client/auth.rs b/server/crates/arbiter-server/src/peers/client/auth.rs index e21068e..2152ace 100644 --- a/server/crates/arbiter-server/src/peers/client/auth.rs +++ b/server/crates/arbiter-server/src/peers/client/auth.rs @@ -14,7 +14,9 @@ use tracing::error; use crate::{ actors::{ - GlobalActors, flow_coordinator::{self, RequestClientApproval}, vault::Vault + GlobalActors, + flow_coordinator::{self, RequestClientApproval}, + vault::Vault, }, crypto::integrity::{self, AttestationStatus}, db::{ @@ -187,10 +189,7 @@ async fn create_nonce( .await } -async fn approve_new_client( - actors: &GlobalActors, - profile: ClientProfile, -) -> Result<(), Error> { +async fn approve_new_client(actors: &GlobalActors, profile: ClientProfile) -> Result<(), Error> { let result = actors .flow_coordinator .ask(RequestClientApproval { client: profile }) diff --git a/server/crates/arbiter-server/src/peers/client/mod.rs b/server/crates/arbiter-server/src/peers/client/mod.rs index b058b94..79e79ff 100644 --- a/server/crates/arbiter-server/src/peers/client/mod.rs +++ b/server/crates/arbiter-server/src/peers/client/mod.rs @@ -6,7 +6,8 @@ use tracing::{error, info}; use crate::{ actors::GlobalActors, crypto::integrity::{Integrable, hashing::Hashable}, - db, peers::client::session::ClientSession, + db, + peers::client::session::ClientSession, }; #[derive(Debug, Clone)] diff --git a/server/crates/arbiter-server/src/peers/mod.rs b/server/crates/arbiter-server/src/peers/mod.rs index c4d3e03..02b992f 100644 --- a/server/crates/arbiter-server/src/peers/mod.rs +++ b/server/crates/arbiter-server/src/peers/mod.rs @@ -1,2 +1,2 @@ +pub mod client; pub mod user_agent; -pub mod client; \ No newline at end of file diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth.rs b/server/crates/arbiter-server/src/peers/user_agent/auth.rs index 2972d0c..dbf1740 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth.rs @@ -69,7 +69,7 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { pub async fn authenticate( props: &mut UserAgentConnection, transport: T, -) -> Result +) -> Result<(i32, authn::PublicKey), Error> where T: Bi> + Send, { @@ -82,7 +82,7 @@ where }; match state.process_event(parse_auth_event(payload)).await { - Ok(AuthStates::AuthOk(key)) => return Ok(key.clone()), + Ok(AuthStates::AuthOk(result)) => return Ok((result.id, result.pubkey.clone())), Err(AuthError::ActionFailed(err)) => { error!(?err, "State machine action failed"); return Err(err); diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index 8a3abe6..91a7b86 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -1,18 +1,15 @@ +use super::super::{UserAgentConnection, UserAgentCredentials}; use arbiter_crypto::authn::{self, USERAGENT_CONTEXT}; use arbiter_proto::transport::Bi; use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update}; use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::actor::ActorRef; use tracing::error; -use super::super::{UserAgentCredentials, UserAgentConnection}; use super::Error; use crate::peers::user_agent::auth::Outbound; use crate::{ - actors::{ - bootstrap::ConsumeToken, - vault::Vault, - }, + actors::{bootstrap::ConsumeToken, vault::Vault}, crypto::integrity, db::{DatabasePool, schema::useragent_client}, }; @@ -27,6 +24,7 @@ pub struct BootstrapAuthRequest { } pub struct ChallengeContext { + pub id: i32, pub challenge_nonce: i32, pub key: authn::PublicKey, } @@ -35,13 +33,18 @@ pub struct ChallengeSolution { pub solution: Vec, } +pub struct AuthOk { + pub id: i32, + pub pubkey: authn::PublicKey, +} + smlang::statemachine!( name: Auth, custom_error: true, transitions: { *Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext), - Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(authn::PublicKey), - SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(authn::PublicKey), + Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthOk), + SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthOk), } ); @@ -110,12 +113,12 @@ async fn create_nonce( db: &DatabasePool, vault: &ActorRef, pubkey: &authn::PublicKey, -) -> Result { +) -> Result<(i32, i32), Error> { let mut db_conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); Error::internal("Database unavailable") })?; - let new_nonce = db_conn + let (id, new_nonce) = db_conn .exclusive_transaction(|conn| { Box::pin(async move { let (id, new_nonce): (i32, i32) = update(useragent_client::table) @@ -144,59 +147,36 @@ async fn create_nonce( Error::internal("Database error") })?; - Result::<_, Error>::Ok(new_nonce) + Result::<_, Error>::Ok((id, new_nonce)) }) }) .await?; - Ok(new_nonce) + Ok((id, new_nonce)) } -async fn register_key( - db: &DatabasePool, - vault: &ActorRef, - pubkey: &authn::PublicKey, -) -> Result<(), Error> { +async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result { let pubkey_bytes = pubkey.to_bytes(); let mut conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); Error::internal("Database unavailable") })?; - conn.transaction(|conn| { - Box::pin(async move { - const NONCE_START: i32 = 1; + const NONCE_START: i32 = 1; - let id: i32 = diesel::insert_into(useragent_client::table) - .values(( - useragent_client::public_key.eq(pubkey_bytes), - useragent_client::nonce.eq(NONCE_START), - )) - .returning(useragent_client::id) - .get_result(conn) - .await - .map_err(|e| { - error!(error = ?e, "Database error"); - Error::internal("Database operation failed") - })?; + let id: i32 = diesel::insert_into(useragent_client::table) + .values(( + useragent_client::public_key.eq(pubkey_bytes), + useragent_client::nonce.eq(NONCE_START), + )) + .returning(useragent_client::id) + .get_result(&mut conn) + .await + .map_err(|e| { + error!(error = ?e, "Database error"); + Error::internal("Database operation failed") + })?; - let entity = UserAgentCredentials { - pubkey: pubkey.clone(), - nonce: NONCE_START, - }; - - integrity::sign_entity(conn, vault, &entity, id) - .await - .map_err(|e| { - error!(error = ?e, "Failed to sign integrity tag for new user-agent key"); - Error::internal("Failed to register public key") - })?; - - Result::<_, Error>::Ok(()) - }) - }) - .await?; - - Ok(()) + Ok(id) } pub struct AuthContext<'a, T> { @@ -222,7 +202,7 @@ where ) -> Result { verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; - let nonce = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + let (id, nonce) = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; self.transport .send(Ok(Outbound::AuthChallenge { nonce })) @@ -233,6 +213,7 @@ where })?; Ok(ChallengeContext { + id, challenge_nonce: nonce, key: pubkey, }) @@ -243,7 +224,7 @@ where async fn verify_bootstrap_token( &mut self, BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest, - ) -> Result { + ) -> Result { let token_ok: bool = self .conn .actors @@ -264,12 +245,12 @@ where match token_ok { true => { - register_key(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + let id = register_key(&self.conn.db, &pubkey).await?; self.transport .send(Ok(Outbound::AuthSuccess)) .await .map_err(|_| Error::Transport)?; - Ok(pubkey) + Ok(AuthOk { id, pubkey }) } false => { error!("Invalid bootstrap token provided"); @@ -287,11 +268,12 @@ where async fn verify_solution( &mut self, ChallengeContext { + id, challenge_nonce, key, }: &ChallengeContext, ChallengeSolution { solution }: ChallengeSolution, - ) -> Result { + ) -> Result { let signature = authn::Signature::try_from(solution.as_slice()).map_err(|_| { error!("Failed to decode signature in challenge solution"); Error::InvalidChallengeSolution @@ -305,7 +287,7 @@ where .send(Ok(Outbound::AuthSuccess)) .await .map_err(|_| Error::Transport)?; - Ok(key.clone()) + Ok(AuthOk { id: *id, pubkey: key.clone() }) } false => { self.transport diff --git a/server/crates/arbiter-server/src/peers/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/mod.rs index 83fdbbb..4168732 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/mod.rs @@ -1,7 +1,5 @@ use crate::{ - actors::GlobalActors, - crypto::integrity::Integrable, - db, peers::client::ClientProfile, + actors::GlobalActors, crypto::integrity::Integrable, db, peers::client::ClientProfile, }; use arbiter_crypto::authn; diff --git a/server/crates/arbiter-server/src/peers/user_agent/session.rs b/server/crates/arbiter-server/src/peers/user_agent/session.rs index 23a2fb1..8e94931 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session.rs @@ -1,14 +1,22 @@ use arbiter_crypto::authn; +use diesel::{ExpressionMethods, QueryDsl}; +use diesel_async::{AsyncConnection, RunQueryDsl}; +use kameo_actors::message_bus::Register; use std::{borrow::Cow, collections::HashMap}; use arbiter_proto::transport::Sender; use async_trait::async_trait; -use kameo::{Actor, actor::ActorRef, messages}; +use kameo::{Actor, actor::ActorRef, messages, prelude::Message}; use thiserror::Error; use tracing::error; -use crate::{actors::flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, peers::client::ClientProfile}; +use crate::{ + actors::{ + flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, + vault::events, + }, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::UserAgentCredentials} +}; mod state; use state::{DummyContext, UserAgentEvents, UserAgentStateMachine}; @@ -50,6 +58,8 @@ pub struct PendingClientApproval { } pub struct UserAgentSession { + id: i32, + pubkey: authn::PublicKey, props: UserAgentConnection, state: UserAgentStateMachine, sender: Box>, @@ -60,31 +70,22 @@ pub struct UserAgentSession { pub mod connection; impl UserAgentSession { - pub(crate) fn new(props: UserAgentConnection, sender: Box>) -> Self { + pub(crate) fn new( + props: UserAgentConnection, + id: i32, + pubkey: authn::PublicKey, + sender: Box>, + ) -> Self { Self { + id, props, + pubkey, state: UserAgentStateMachine::new(DummyContext), sender, pending_client_approvals: Default::default(), } } - pub fn new_test(db: crate::db::DatabasePool, actors: crate::actors::GlobalActors) -> Self { - struct DummySender; - - #[async_trait] - impl Sender for DummySender { - async fn send( - &mut self, - _item: OutOfBand, - ) -> Result<(), arbiter_proto::transport::Error> { - Ok(()) - } - } - - Self::new(UserAgentConnection::new(db, actors), Box::new(DummySender)) - } - fn transition(&mut self, event: UserAgentEvents) -> Result<(), Error> { self.state.process_event(event).map_err(|e| { error!(?e, "State transition failed"); @@ -127,6 +128,61 @@ impl UserAgentSession { } } +impl Message for UserAgentSession { + type Reply = Result<(), Error>; + + async fn handle( + &mut self, + _: events::VaultBootstrapped, + ctx: &mut kameo::prelude::Context, + ) -> Self::Reply { + let Ok(mut conn) = self.props.db.get().await else { + error!("Failed to get database connection for vault bootstrapped event"); + ctx.stop(); + return Err(Error::internal("Failed to get database connection")); + }; + + + let result = conn.exclusive_transaction(|conn| { + Box::pin(async { + let nonce: i32 = useragent_client::table + .filter(useragent_client::id.eq(self.id)) + .select(useragent_client::nonce) + .first::(conn) + .await + .map_err(|e| { + error!(?e, "Failed to get nonce for useragent bootstrapping"); + Error::internal("Failed to sign user agent credentials") + })?; + + let entity = UserAgentCredentials { + pubkey: self.pubkey.clone(), + nonce, + }; + + integrity::sign_entity(conn, &self.props.actors.vault, &entity, self.id) + .await + .map_err(|e| { + error!(?e, "Failed to sign user agent credentials during vault bootstrapping"); + Error::internal("Failed to sign user agent credentials") + })?; + + Result::<_, Error>::Ok(()) + }) + }).await; + + match result { + Ok(_) => Ok(()), + Err(err) => { + error!(?err, "Error during vault bootstrapping"); + ctx.stop(); + Err(err) + }, + } + + } +} + impl Actor for UserAgentSession { type Args = Self; @@ -136,6 +192,21 @@ impl Actor for UserAgentSession { args: Self::Args, this: kameo::prelude::ActorRef, ) -> Result { + args.props + .actors + .events + .tell(Register( + this.clone().recipient::(), + )) + .await + .map_err(|err| { + error!( + ?err, + "Failed to register user agent connection with event bus" + ); + Error::internal("Failed to register user agent connection with event bus") + })?; + args.props .actors .flow_coordinator diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs b/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs index 119800f..b8e9d2a 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs @@ -14,7 +14,7 @@ use kameo::prelude::Context; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; -use crate::{actors::vault::VaultState, peers::user_agent::session::state::{UnsealContext, UserAgentEvents}}; +use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer; use crate::actors::{ evm::{ ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, @@ -27,10 +27,11 @@ use crate::db::models::{ }; use crate::evm::policies::{Grant, SpecificGrant}; use crate::{ - actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer, + actors::vault::VaultState, + peers::user_agent::session::state::{UnsealContext, UserAgentEvents}, }; -use super::{UserAgentSession, state, Error}; +use super::{Error, UserAgentSession, state}; impl UserAgentSession { fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> { diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 80e2652..71f989c 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -5,14 +5,10 @@ use arbiter_crypto::{ use arbiter_proto::ClientMetadata; use arbiter_proto::transport::{Receiver, Sender}; use arbiter_server::{ - actors::{ - GlobalActors, - - vault::Bootstrap, - }, - peers::client::{ClientConnection, ClientCredentials, auth, connect_client}, + actors::{GlobalActors, vault::Bootstrap}, crypto::integrity, db::{self, schema}, + peers::client::{ClientConnection, ClientCredentials, auth, connect_client}, }; use diesel::{ExpressionMethods as _, NullableExpressionMethods as _, QueryDsl as _, insert_into}; use diesel_async::RunQueryDsl; diff --git a/server/crates/arbiter-server/tests/common/mod.rs b/server/crates/arbiter-server/tests/common/mod.rs index 17676d4..d4aa438 100644 --- a/server/crates/arbiter-server/tests/common/mod.rs +++ b/server/crates/arbiter-server/tests/common/mod.rs @@ -12,7 +12,9 @@ use tokio::sync::mpsc; #[allow(dead_code)] pub async fn bootstrapped_vault(db: &db::DatabasePool) -> Vault { - let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); actor .bootstrap(SafeCell::new(b"test-seal-key".to_vec())) .await diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index 31d6786..c0665f7 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -5,15 +5,10 @@ use arbiter_crypto::{ use arbiter_proto::transport::{Receiver, Sender}; use arbiter_server::{ - actors::{ - GlobalActors, - bootstrap::GetToken, - vault::Bootstrap, - - }, - peers::user_agent::{UserAgentConnection, UserAgentCredentials, auth}, + actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, crypto::integrity, db::{self, schema}, + peers::user_agent::{UserAgentConnection, UserAgentCredentials, auth}, }; use diesel::{ExpressionMethods as _, QueryDsl, insert_into}; use diesel_async::RunQueryDsl; diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index 59ef627..ba2388b 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -3,13 +3,12 @@ use arbiter_server::{ actors::{ GlobalActors, vault::{Bootstrap, Seal}, - }, - peers::user_agent::{ - UserAgentSession, - session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, - }, db, + peers::user_agent::{ + UserAgentSession, + session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, + }, }; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; diff --git a/server/crates/arbiter-server/tests/vault/concurrency.rs b/server/crates/arbiter-server/tests/vault/concurrency.rs index 777b203..f6e8a88 100644 --- a/server/crates/arbiter-server/tests/vault/concurrency.rs +++ b/server/crates/arbiter-server/tests/vault/concurrency.rs @@ -2,7 +2,10 @@ use std::collections::{HashMap, HashSet}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ - actors::{GlobalActors, vault::{CreateNew, Error, Vault}}, + actors::{ + GlobalActors, + vault::{CreateNew, Error, Vault}, + }, db::{self, models, schema}, }; @@ -161,7 +164,9 @@ async fn decrypt_roundtrip_after_high_concurrency() { let writes = write_concurrently(actor, "roundtrip", 40).await; let expected: HashMap> = writes.into_iter().collect(); - let mut decryptor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); + let mut decryptor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); decryptor .try_unseal(SafeCell::new(b"test-seal-key".to_vec())) .await diff --git a/server/crates/arbiter-server/tests/vault/lifecycle.rs b/server/crates/arbiter-server/tests/vault/lifecycle.rs index 95cc52f..ddfb602 100644 --- a/server/crates/arbiter-server/tests/vault/lifecycle.rs +++ b/server/crates/arbiter-server/tests/vault/lifecycle.rs @@ -1,12 +1,11 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_server::{ - actors::{GlobalActors, vault::{Error, Vault}}, + actors::{ + GlobalActors, + vault::{Error, Vault}, + }, crypto::encryption::v1::{Nonce, ROOT_KEY_TAG}, db::{self, models, schema}, - peers::user_agent::{ - UserAgentSession, - session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, - }, }; use diesel::{QueryDsl, SelectableHelper}; @@ -18,7 +17,9 @@ use crate::common; #[test_log::test] async fn test_bootstrap() { let db = db::create_test_pool().await; - let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.bootstrap(seal_key).await.unwrap(); @@ -52,7 +53,9 @@ async fn test_bootstrap_rejects_double() { #[test_log::test] async fn test_create_new_before_bootstrap_fails() { let db = db::create_test_pool().await; - let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()) + .await + .unwrap(); let err = actor .create_new(SafeCell::new(b"data".to_vec())) @@ -65,7 +68,9 @@ async fn test_create_new_before_bootstrap_fails() { #[test_log::test] async fn test_decrypt_before_bootstrap_fails() { let db = db::create_test_pool().await; - let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()) + .await + .unwrap(); let err = actor.decrypt(1).await.unwrap_err(); assert!(matches!(err, Error::NotBootstrapped)); @@ -78,7 +83,9 @@ async fn test_new_restores_sealed_state() { let actor = common::bootstrapped_vault(&db).await; drop(actor); - let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus()) + .await + .unwrap(); let err = actor2.decrypt(1).await.unwrap_err(); assert!(matches!(err, Error::NotBootstrapped)); } @@ -96,7 +103,9 @@ async fn test_unseal_correct_password() { .unwrap(); drop(actor); - let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); let seal_key = SafeCell::new(b"test-seal-key".to_vec()); actor.try_unseal(seal_key).await.unwrap(); @@ -117,7 +126,9 @@ async fn test_unseal_wrong_then_correct_password() { .unwrap(); drop(actor); - let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap(); + let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) + .await + .unwrap(); let bad_key = SafeCell::new(b"wrong-password".to_vec()); let err = actor.try_unseal(bad_key).await.unwrap_err(); diff --git a/useragent/lib/features/connection/auth.dart b/useragent/lib/features/connection/auth.dart index cd512d7..9ae0490 100644 --- a/useragent/lib/features/connection/auth.dart +++ b/useragent/lib/features/connection/auth.dart @@ -61,11 +61,6 @@ Future connectAndAuthorize( final req = ua_auth.AuthChallengeRequest( pubkey: pubkey, bootstrapToken: bootstrapToken, - keyType: switch (key.alg) { - KeyAlgorithm.rsa => ua_auth.KeyType.KEY_TYPE_RSA, - KeyAlgorithm.ecdsa => ua_auth.KeyType.KEY_TYPE_ECDSA_SECP256K1, - KeyAlgorithm.ed25519 => ua_auth.KeyType.KEY_TYPE_ED25519, - }, ); final response = await connection.ask( UserAgentRequest(auth: ua_auth.Request(challengeRequest: req)), diff --git a/useragent/lib/proto/shared/evm.pb.dart b/useragent/lib/proto/shared/evm.pb.dart index 4857f1b..cf2b7f9 100644 --- a/useragent/lib/proto/shared/evm.pb.dart +++ b/useragent/lib/proto/shared/evm.pb.dart @@ -414,6 +414,79 @@ class GasLimitExceededViolation extends $pb.GeneratedMessage { void clearMaxPriorityFeePerGas() => $_clearField(2); } +class EvalViolation_ChainIdMismatch extends $pb.GeneratedMessage { + factory EvalViolation_ChainIdMismatch({ + $fixnum.Int64? expected, + $fixnum.Int64? actual, + }) { + final result = create(); + if (expected != null) result.expected = expected; + if (actual != null) result.actual = actual; + return result; + } + + EvalViolation_ChainIdMismatch._(); + + factory EvalViolation_ChainIdMismatch.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EvalViolation_ChainIdMismatch.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EvalViolation.ChainIdMismatch', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.shared.evm'), + createEmptyInstance: create) + ..a<$fixnum.Int64>( + 1, _omitFieldNames ? '' : 'expected', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'actual', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvalViolation_ChainIdMismatch clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EvalViolation_ChainIdMismatch copyWith( + void Function(EvalViolation_ChainIdMismatch) updates) => + super.copyWith( + (message) => updates(message as EvalViolation_ChainIdMismatch)) + as EvalViolation_ChainIdMismatch; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EvalViolation_ChainIdMismatch create() => + EvalViolation_ChainIdMismatch._(); + @$core.override + EvalViolation_ChainIdMismatch createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EvalViolation_ChainIdMismatch getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EvalViolation_ChainIdMismatch? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get expected => $_getI64(0); + @$pb.TagNumber(1) + set expected($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasExpected() => $_has(0); + @$pb.TagNumber(1) + void clearExpected() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get actual => $_getI64(1); + @$pb.TagNumber(2) + set actual($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasActual() => $_has(1); + @$pb.TagNumber(2) + void clearActual() => $_clearField(2); +} + enum EvalViolation_Kind { invalidTarget, gasLimitExceeded, @@ -421,6 +494,7 @@ enum EvalViolation_Kind { volumetricLimitExceeded, invalidTime, invalidTransactionType, + chainIdMismatch, notSet } @@ -432,6 +506,7 @@ class EvalViolation extends $pb.GeneratedMessage { $0.Empty? volumetricLimitExceeded, $0.Empty? invalidTime, $0.Empty? invalidTransactionType, + EvalViolation_ChainIdMismatch? chainIdMismatch, }) { final result = create(); if (invalidTarget != null) result.invalidTarget = invalidTarget; @@ -442,6 +517,7 @@ class EvalViolation extends $pb.GeneratedMessage { if (invalidTime != null) result.invalidTime = invalidTime; if (invalidTransactionType != null) result.invalidTransactionType = invalidTransactionType; + if (chainIdMismatch != null) result.chainIdMismatch = chainIdMismatch; return result; } @@ -462,6 +538,7 @@ class EvalViolation extends $pb.GeneratedMessage { 4: EvalViolation_Kind.volumetricLimitExceeded, 5: EvalViolation_Kind.invalidTime, 6: EvalViolation_Kind.invalidTransactionType, + 7: EvalViolation_Kind.chainIdMismatch, 0: EvalViolation_Kind.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -469,7 +546,7 @@ class EvalViolation extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.shared.evm'), createEmptyInstance: create) - ..oo(0, [1, 2, 3, 4, 5, 6]) + ..oo(0, [1, 2, 3, 4, 5, 6, 7]) ..a<$core.List<$core.int>>( 1, _omitFieldNames ? '' : 'invalidTarget', $pb.PbFieldType.OY) ..aOM( @@ -483,6 +560,9 @@ class EvalViolation extends $pb.GeneratedMessage { subBuilder: $0.Empty.create) ..aOM<$0.Empty>(6, _omitFieldNames ? '' : 'invalidTransactionType', subBuilder: $0.Empty.create) + ..aOM( + 7, _omitFieldNames ? '' : 'chainIdMismatch', + subBuilder: EvalViolation_ChainIdMismatch.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -510,6 +590,7 @@ class EvalViolation extends $pb.GeneratedMessage { @$pb.TagNumber(4) @$pb.TagNumber(5) @$pb.TagNumber(6) + @$pb.TagNumber(7) EvalViolation_Kind whichKind() => _EvalViolation_KindByTag[$_whichOneof(0)]!; @$pb.TagNumber(1) @$pb.TagNumber(2) @@ -517,6 +598,7 @@ class EvalViolation extends $pb.GeneratedMessage { @$pb.TagNumber(4) @$pb.TagNumber(5) @$pb.TagNumber(6) + @$pb.TagNumber(7) void clearKind() => $_clearField($_whichOneof(0)); @$pb.TagNumber(1) @@ -582,6 +664,18 @@ class EvalViolation extends $pb.GeneratedMessage { void clearInvalidTransactionType() => $_clearField(6); @$pb.TagNumber(6) $0.Empty ensureInvalidTransactionType() => $_ensure(5); + + @$pb.TagNumber(7) + EvalViolation_ChainIdMismatch get chainIdMismatch => $_getN(6); + @$pb.TagNumber(7) + set chainIdMismatch(EvalViolation_ChainIdMismatch value) => + $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasChainIdMismatch() => $_has(6); + @$pb.TagNumber(7) + void clearChainIdMismatch() => $_clearField(7); + @$pb.TagNumber(7) + EvalViolation_ChainIdMismatch ensureChainIdMismatch() => $_ensure(6); } /// Transaction was classified but no grant covers it diff --git a/useragent/lib/proto/shared/evm.pbjson.dart b/useragent/lib/proto/shared/evm.pbjson.dart index 3ae3545..77c31ec 100644 --- a/useragent/lib/proto/shared/evm.pbjson.dart +++ b/useragent/lib/proto/shared/evm.pbjson.dart @@ -195,12 +195,31 @@ const EvalViolation$json = { '9': 0, '10': 'invalidTransactionType' }, + { + '1': 'chain_id_mismatch', + '3': 7, + '4': 1, + '5': 11, + '6': '.arbiter.shared.evm.EvalViolation.ChainIdMismatch', + '9': 0, + '10': 'chainIdMismatch' + }, ], + '3': [EvalViolation_ChainIdMismatch$json], '8': [ {'1': 'kind'}, ], }; +@$core.Deprecated('Use evalViolationDescriptor instead') +const EvalViolation_ChainIdMismatch$json = { + '1': 'ChainIdMismatch', + '2': [ + {'1': 'expected', '3': 1, '4': 1, '5': 4, '10': 'expected'}, + {'1': 'actual', '3': 2, '4': 1, '5': 4, '10': 'actual'}, + ], +}; + /// Descriptor for `EvalViolation`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List evalViolationDescriptor = $convert.base64Decode( 'Cg1FdmFsVmlvbGF0aW9uEicKDmludmFsaWRfdGFyZ2V0GAEgASgMSABSDWludmFsaWRUYXJnZX' @@ -211,7 +230,10 @@ final $typed_data.Uint8List evalViolationDescriptor = $convert.base64Decode( 'YuRW1wdHlIAFIXdm9sdW1ldHJpY0xpbWl0RXhjZWVkZWQSOwoMaW52YWxpZF90aW1lGAUgASgL' 'MhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SABSC2ludmFsaWRUaW1lElIKGGludmFsaWRfdHJhbn' 'NhY3Rpb25fdHlwZRgGIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eUgAUhZpbnZhbGlkVHJh' - 'bnNhY3Rpb25UeXBlQgYKBGtpbmQ='); + 'bnNhY3Rpb25UeXBlEl8KEWNoYWluX2lkX21pc21hdGNoGAcgASgLMjEuYXJiaXRlci5zaGFyZW' + 'QuZXZtLkV2YWxWaW9sYXRpb24uQ2hhaW5JZE1pc21hdGNoSABSD2NoYWluSWRNaXNtYXRjaBpF' + 'Cg9DaGFpbklkTWlzbWF0Y2gSGgoIZXhwZWN0ZWQYASABKARSCGV4cGVjdGVkEhYKBmFjdHVhbB' + 'gCIAEoBFIGYWN0dWFsQgYKBGtpbmQ='); @$core.Deprecated('Use noMatchingGrantErrorDescriptor instead') const NoMatchingGrantError$json = { diff --git a/useragent/lib/proto/user_agent/auth.pb.dart b/useragent/lib/proto/user_agent/auth.pb.dart index 8bfea1e..1fa8bbd 100644 --- a/useragent/lib/proto/user_agent/auth.pb.dart +++ b/useragent/lib/proto/user_agent/auth.pb.dart @@ -24,12 +24,10 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { factory AuthChallengeRequest({ $core.List<$core.int>? pubkey, $core.String? bootstrapToken, - KeyType? keyType, }) { final result = create(); if (pubkey != null) result.pubkey = pubkey; if (bootstrapToken != null) result.bootstrapToken = bootstrapToken; - if (keyType != null) result.keyType = keyType; return result; } @@ -50,8 +48,6 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { ..a<$core.List<$core.int>>( 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) ..aOS(2, _omitFieldNames ? '' : 'bootstrapToken') - ..aE(3, _omitFieldNames ? '' : 'keyType', - enumValues: KeyType.values) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -90,15 +86,6 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { $core.bool hasBootstrapToken() => $_has(1); @$pb.TagNumber(2) void clearBootstrapToken() => $_clearField(2); - - @$pb.TagNumber(3) - KeyType get keyType => $_getN(2); - @$pb.TagNumber(3) - set keyType(KeyType value) => $_setField(3, value); - @$pb.TagNumber(3) - $core.bool hasKeyType() => $_has(2); - @$pb.TagNumber(3) - void clearKeyType() => $_clearField(3); } class AuthChallenge extends $pb.GeneratedMessage { diff --git a/useragent/lib/proto/user_agent/auth.pbenum.dart b/useragent/lib/proto/user_agent/auth.pbenum.dart index 853cbed..835ae9c 100644 --- a/useragent/lib/proto/user_agent/auth.pbenum.dart +++ b/useragent/lib/proto/user_agent/auth.pbenum.dart @@ -14,31 +14,6 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; -class KeyType extends $pb.ProtobufEnum { - static const KeyType KEY_TYPE_UNSPECIFIED = - KeyType._(0, _omitEnumNames ? '' : 'KEY_TYPE_UNSPECIFIED'); - static const KeyType KEY_TYPE_ED25519 = - KeyType._(1, _omitEnumNames ? '' : 'KEY_TYPE_ED25519'); - static const KeyType KEY_TYPE_ECDSA_SECP256K1 = - KeyType._(2, _omitEnumNames ? '' : 'KEY_TYPE_ECDSA_SECP256K1'); - static const KeyType KEY_TYPE_RSA = - KeyType._(3, _omitEnumNames ? '' : 'KEY_TYPE_RSA'); - - static const $core.List values = [ - KEY_TYPE_UNSPECIFIED, - KEY_TYPE_ED25519, - KEY_TYPE_ECDSA_SECP256K1, - KEY_TYPE_RSA, - ]; - - static final $core.List _byValue = - $pb.ProtobufEnum.$_initByValueList(values, 3); - static KeyType? valueOf($core.int value) => - value < 0 || value >= _byValue.length ? null : _byValue[value]; - - const KeyType._(super.value, super.name); -} - class AuthResult extends $pb.ProtobufEnum { static const AuthResult AUTH_RESULT_UNSPECIFIED = AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED'); diff --git a/useragent/lib/proto/user_agent/auth.pbjson.dart b/useragent/lib/proto/user_agent/auth.pbjson.dart index 87fc1c6..7f7fe15 100644 --- a/useragent/lib/proto/user_agent/auth.pbjson.dart +++ b/useragent/lib/proto/user_agent/auth.pbjson.dart @@ -15,22 +15,6 @@ import 'dart:convert' as $convert; import 'dart:core' as $core; import 'dart:typed_data' as $typed_data; -@$core.Deprecated('Use keyTypeDescriptor instead') -const KeyType$json = { - '1': 'KeyType', - '2': [ - {'1': 'KEY_TYPE_UNSPECIFIED', '2': 0}, - {'1': 'KEY_TYPE_ED25519', '2': 1}, - {'1': 'KEY_TYPE_ECDSA_SECP256K1', '2': 2}, - {'1': 'KEY_TYPE_RSA', '2': 3}, - ], -}; - -/// Descriptor for `KeyType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List keyTypeDescriptor = $convert.base64Decode( - 'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR' - 'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD'); - @$core.Deprecated('Use authResultDescriptor instead') const AuthResult$json = { '1': 'AuthResult', @@ -67,14 +51,6 @@ const AuthChallengeRequest$json = { '10': 'bootstrapToken', '17': true }, - { - '1': 'key_type', - '3': 3, - '4': 1, - '5': 14, - '6': '.arbiter.user_agent.auth.KeyType', - '10': 'keyType' - }, ], '8': [ {'1': '_bootstrap_token'}, @@ -84,9 +60,8 @@ const AuthChallengeRequest$json = { /// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode( 'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIsCg9ib290c3' - 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQESOwoIa2V5X3R5cGUYAyABKA4y' - 'IC5hcmJpdGVyLnVzZXJfYWdlbnQuYXV0aC5LZXlUeXBlUgdrZXlUeXBlQhIKEF9ib290c3RyYX' - 'BfdG9rZW4='); + 'RyYXBfdG9rZW4YAiABKAlIAFIOYm9vdHN0cmFwVG9rZW6IAQFCEgoQX2Jvb3RzdHJhcF90b2tl' + 'bg=='); @$core.Deprecated('Use authChallengeDescriptor instead') const AuthChallenge$json = { From a4070e7df722ee5d670dc2717f87923443e899e1 Mon Sep 17 00:00:00 2001 From: hdbg Date: Tue, 7 Apr 2026 15:41:50 +0200 Subject: [PATCH 03/13] fix(useragent): unsafe, but working implementation of ml-dsa --- mise.lock | 4 + mise.toml | 1 + server/crates/arbiter-crypto/Cargo.toml | 2 +- useragent/flutter_rust_bridge.yaml | 3 + .../lib/features/callouts/callout_event.dart | 5 +- .../lib/features/callouts/show_callout.dart | 38 +- .../features/callouts/show_callout_list.dart | 7 +- .../lib/features/connection/arbiter_url.dart | 4 +- useragent/lib/features/connection/auth.dart | 4 +- .../lib/features/connection/connection.dart | 4 +- useragent/lib/features/connection/evm.dart | 8 +- .../lib/features/connection/evm/grants.dart | 16 +- .../connection/evm/wallet_access.dart | 12 +- .../connection/server_info_storage.dart | 4 +- useragent/lib/features/connection/vault.dart | 7 +- .../lib/features/identity/hazmat_mldsa.dart | 71 + .../lib/features/identity/pk_manager.dart | 7 +- .../lib/features/identity/simple_ed25519.dart | 93 - useragent/lib/main.dart | 6 +- .../providers/connection/bootstrap_token.dart | 2 - useragent/lib/providers/evm/evm.dart | 5 +- useragent/lib/providers/key.dart | 4 +- useragent/lib/providers/sdk_clients/list.dart | 4 +- useragent/lib/providers/server_info.dart | 4 +- .../lib/screens/callouts/sdk_connect.dart | 8 +- useragent/lib/screens/dashboard.dart | 2 +- .../screens/dashboard/clients/details.dart | 2 - .../details/widgets/client_summary_card.dart | 42 +- .../widgets/wallet_access_save_bar.dart | 40 +- .../widgets/wallet_access_section.dart | 37 +- .../lib/screens/dashboard/clients/table.dart | 74 +- .../create/fields/client_picker_field.dart | 4 +- .../grants/create/fields/date_time_field.dart | 86 +- .../fields/wallet_access_picker_field.dart | 4 +- .../create/grants/ether_transfer_grant.dart | 12 +- .../create/grants/token_transfer_grant.dart | 32 +- .../dashboard/evm/grants/create/screen.dart | 49 +- .../grants/create/shared_grant_fields.dart | 1 - .../evm/grants/widgets/grant_card.dart | 14 +- .../screens/dashboard/evm/wallets/header.dart | 2 - .../screens/dashboard/evm/wallets/table.dart | 81 +- useragent/lib/src/rust/api.dart | 22 + useragent/lib/src/rust/frb_generated.dart | 573 ++ useragent/lib/src/rust/frb_generated.io.dart | 211 + useragent/lib/src/rust/frb_generated.web.dart | 203 + useragent/lib/widgets/bottom_popup.dart | 13 +- useragent/lib/widgets/state_panel.dart | 66 +- useragent/macos/Podfile.lock | 6 + useragent/pubspec.lock | 66 +- useragent/pubspec.yaml | 7 +- useragent/rust/.gitignore | 1 + useragent/rust/Cargo.lock | 5086 +++++++++++++++++ useragent/rust/Cargo.toml | 17 + useragent/rust/src/api/mod.rs | 29 + useragent/rust/src/frb_generated.rs | 558 ++ useragent/rust/src/lib.rs | 2 + useragent/rust_builder/.gitignore | 29 + useragent/rust_builder/README.md | 1 + useragent/rust_builder/android/.gitignore | 9 + useragent/rust_builder/android/build.gradle | 56 + .../rust_builder/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + useragent/rust_builder/cargokit/.gitignore | 4 + useragent/rust_builder/cargokit/LICENSE | 42 + useragent/rust_builder/cargokit/README | 11 + useragent/rust_builder/cargokit/build_pod.sh | 58 + .../cargokit/build_tool/README.md | 5 + .../cargokit/build_tool/analysis_options.yaml | 34 + .../cargokit/build_tool/bin/build_tool.dart | 8 + .../cargokit/build_tool/lib/build_tool.dart | 8 + .../lib/src/android_environment.dart | 195 + .../lib/src/artifacts_provider.dart | 266 + .../build_tool/lib/src/build_cmake.dart | 40 + .../build_tool/lib/src/build_gradle.dart | 49 + .../build_tool/lib/src/build_pod.dart | 89 + .../build_tool/lib/src/build_tool.dart | 276 + .../cargokit/build_tool/lib/src/builder.dart | 209 + .../cargokit/build_tool/lib/src/cargo.dart | 48 + .../build_tool/lib/src/crate_hash.dart | 124 + .../build_tool/lib/src/environment.dart | 68 + .../cargokit/build_tool/lib/src/logging.dart | 52 + .../cargokit/build_tool/lib/src/options.dart | 309 + .../lib/src/precompile_binaries.dart | 205 + .../cargokit/build_tool/lib/src/rustup.dart | 149 + .../cargokit/build_tool/lib/src/target.dart | 147 + .../cargokit/build_tool/lib/src/util.dart | 172 + .../build_tool/lib/src/verify_binaries.dart | 84 + .../cargokit/build_tool/pubspec.lock | 453 ++ .../cargokit/build_tool/pubspec.yaml | 33 + .../cargokit/cmake/cargokit.cmake | 99 + .../cargokit/cmake/resolve_symlinks.ps1 | 34 + .../cargokit/gradle/plugin.gradle | 184 + .../rust_builder/cargokit/run_build_tool.cmd | 91 + .../rust_builder/cargokit/run_build_tool.sh | 99 + .../rust_builder/ios/Classes/dummy_file.c | 1 + .../rust_builder/ios/rust_lib_arbiter.podspec | 45 + useragent/rust_builder/linux/CMakeLists.txt | 19 + .../rust_builder/macos/Classes/dummy_file.c | 1 + .../macos/rust_lib_arbiter.podspec | 44 + useragent/rust_builder/pubspec.yaml | 34 + useragent/rust_builder/windows/.gitignore | 17 + useragent/rust_builder/windows/CMakeLists.txt | 20 + useragent/test_driver/integration_test.dart | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 104 files changed, 11133 insertions(+), 461 deletions(-) create mode 100644 useragent/flutter_rust_bridge.yaml create mode 100644 useragent/lib/features/identity/hazmat_mldsa.dart delete mode 100644 useragent/lib/features/identity/simple_ed25519.dart create mode 100644 useragent/lib/src/rust/api.dart create mode 100644 useragent/lib/src/rust/frb_generated.dart create mode 100644 useragent/lib/src/rust/frb_generated.io.dart create mode 100644 useragent/lib/src/rust/frb_generated.web.dart create mode 100644 useragent/rust/.gitignore create mode 100644 useragent/rust/Cargo.lock create mode 100644 useragent/rust/Cargo.toml create mode 100644 useragent/rust/src/api/mod.rs create mode 100644 useragent/rust/src/frb_generated.rs create mode 100644 useragent/rust/src/lib.rs create mode 100644 useragent/rust_builder/.gitignore create mode 100644 useragent/rust_builder/README.md create mode 100644 useragent/rust_builder/android/.gitignore create mode 100644 useragent/rust_builder/android/build.gradle create mode 100644 useragent/rust_builder/android/settings.gradle create mode 100644 useragent/rust_builder/android/src/main/AndroidManifest.xml create mode 100644 useragent/rust_builder/cargokit/.gitignore create mode 100644 useragent/rust_builder/cargokit/LICENSE create mode 100644 useragent/rust_builder/cargokit/README create mode 100755 useragent/rust_builder/cargokit/build_pod.sh create mode 100644 useragent/rust_builder/cargokit/build_tool/README.md create mode 100644 useragent/rust_builder/cargokit/build_tool/analysis_options.yaml create mode 100644 useragent/rust_builder/cargokit/build_tool/bin/build_tool.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/build_tool.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/android_environment.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/build_pod.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/build_tool.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/builder.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/cargo.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/environment.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/logging.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/options.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/rustup.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/target.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/util.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart create mode 100644 useragent/rust_builder/cargokit/build_tool/pubspec.lock create mode 100644 useragent/rust_builder/cargokit/build_tool/pubspec.yaml create mode 100644 useragent/rust_builder/cargokit/cmake/cargokit.cmake create mode 100644 useragent/rust_builder/cargokit/cmake/resolve_symlinks.ps1 create mode 100644 useragent/rust_builder/cargokit/gradle/plugin.gradle create mode 100755 useragent/rust_builder/cargokit/run_build_tool.cmd create mode 100755 useragent/rust_builder/cargokit/run_build_tool.sh create mode 100644 useragent/rust_builder/ios/Classes/dummy_file.c create mode 100644 useragent/rust_builder/ios/rust_lib_arbiter.podspec create mode 100644 useragent/rust_builder/linux/CMakeLists.txt create mode 100644 useragent/rust_builder/macos/Classes/dummy_file.c create mode 100644 useragent/rust_builder/macos/rust_lib_arbiter.podspec create mode 100644 useragent/rust_builder/pubspec.yaml create mode 100644 useragent/rust_builder/windows/.gitignore create mode 100644 useragent/rust_builder/windows/CMakeLists.txt create mode 100644 useragent/test_driver/integration_test.dart diff --git a/mise.lock b/mise.lock index 56e1cdd..08d8cb1 100644 --- a/mise.lock +++ b/mise.lock @@ -72,6 +72,10 @@ backend = "cargo:diesel_cli" default-features = "false" features = "sqlite,sqlite-bundled" +[[tools."cargo:flutter_rust_bridge_codegen"]] +version = "2.12.0" +backend = "cargo:flutter_rust_bridge_codegen" + [[tools.flutter]] version = "3.38.9-stable" backend = "asdf:flutter" diff --git a/mise.toml b/mise.toml index 168e9f6..5341d6a 100644 --- a/mise.toml +++ b/mise.toml @@ -13,6 +13,7 @@ python = "3.14.3" ast-grep = "0.42.0" "cargo:cargo-edit" = "0.13.9" "cargo:cargo-mutants" = "27.0.0" +"cargo:flutter_rust_bridge_codegen" = "2.12.0" [tasks.codegen] sources = ['protobufs/*.proto', 'protobufs/**/*.proto'] diff --git a/server/crates/arbiter-crypto/Cargo.toml b/server/crates/arbiter-crypto/Cargo.toml index e39c7eb..f5d735a 100644 --- a/server/crates/arbiter-crypto/Cargo.toml +++ b/server/crates/arbiter-crypto/Cargo.toml @@ -15,4 +15,4 @@ workspace = true [features] default = ["authn", "safecell"] authn = ["dep:ml-dsa", "dep:rand", "dep:base64"] -safecell = ["dep:memsafe"] +safecell = ["dep:memsafe"] \ No newline at end of file diff --git a/useragent/flutter_rust_bridge.yaml b/useragent/flutter_rust_bridge.yaml new file mode 100644 index 0000000..e15ed91 --- /dev/null +++ b/useragent/flutter_rust_bridge.yaml @@ -0,0 +1,3 @@ +rust_input: crate::api +rust_root: rust/ +dart_output: lib/src/rust \ No newline at end of file diff --git a/useragent/lib/features/callouts/callout_event.dart b/useragent/lib/features/callouts/callout_event.dart index 32b90dc..99e943b 100644 --- a/useragent/lib/features/callouts/callout_event.dart +++ b/useragent/lib/features/callouts/callout_event.dart @@ -18,7 +18,6 @@ sealed class CalloutEvent with _$CalloutEvent { required CalloutData data, }) = CalloutEventAdded; - const factory CalloutEvent.cancelled({ - required String id, - }) = CalloutEventCancelled; + const factory CalloutEvent.cancelled({required String id}) = + CalloutEventCancelled; } diff --git a/useragent/lib/features/callouts/show_callout.dart b/useragent/lib/features/callouts/show_callout.dart index 5fd8ac0..77e3a48 100644 --- a/useragent/lib/features/callouts/show_callout.dart +++ b/useragent/lib/features/callouts/show_callout.dart @@ -14,11 +14,8 @@ Future showCallout(BuildContext context, WidgetRef ref, String id) async { barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 320), - pageBuilder: (_, animation, _) => _CalloutOverlay( - id: id, - data: data, - animation: animation, - ), + pageBuilder: (_, animation, _) => + _CalloutOverlay(id: id, data: data, animation: animation), ); } @@ -35,22 +32,25 @@ class _CalloutOverlay extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - ref.listen( - calloutManagerProvider.select((map) => map.containsKey(id)), - (wasPresent, isPresent) { - if (wasPresent == true && !isPresent && context.mounted) { - Navigator.of(context).pop(); - } - }, - ); + ref.listen(calloutManagerProvider.select((map) => map.containsKey(id)), ( + wasPresent, + isPresent, + ) { + if (wasPresent == true && !isPresent && context.mounted) { + Navigator.of(context).pop(); + } + }); final content = switch (data) { - ConnectApprovalData(:final pubkey, :final clientInfo) => SdkConnectCallout( - pubkey: pubkey, - clientInfo: clientInfo, - onAccept: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, true), - onDecline: () => ref.read(calloutManagerProvider.notifier).sendDecision(id, false), - ), + ConnectApprovalData(:final pubkey, :final clientInfo) => + SdkConnectCallout( + pubkey: pubkey, + clientInfo: clientInfo, + onAccept: () => + ref.read(calloutManagerProvider.notifier).sendDecision(id, true), + onDecline: () => + ref.read(calloutManagerProvider.notifier).sendDecision(id, false), + ), }; final barrierAnim = CurvedAnimation( diff --git a/useragent/lib/features/callouts/show_callout_list.dart b/useragent/lib/features/callouts/show_callout_list.dart index de235b8..2e72491 100644 --- a/useragent/lib/features/callouts/show_callout_list.dart +++ b/useragent/lib/features/callouts/show_callout_list.dart @@ -14,7 +14,8 @@ Future showCalloutList(BuildContext context, WidgetRef ref) async { barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 280), - pageBuilder: (_, animation, __) => _CalloutListOverlay(animation: animation), + pageBuilder: (_, animation, __) => + _CalloutListOverlay(animation: animation), ); if (selectedId != null && context.mounted) { @@ -51,7 +52,9 @@ class _CalloutListOverlay extends ConsumerWidget { child: AnimatedBuilder( animation: barrierAnim, builder: (_, __) => ColoredBox( - color: Colors.black.withValues(alpha: 0.35 * barrierAnim.value), + color: Colors.black.withValues( + alpha: 0.35 * barrierAnim.value, + ), ), ), ), diff --git a/useragent/lib/features/connection/arbiter_url.dart b/useragent/lib/features/connection/arbiter_url.dart index d71d236..b6edbdf 100644 --- a/useragent/lib/features/connection/arbiter_url.dart +++ b/useragent/lib/features/connection/arbiter_url.dart @@ -50,7 +50,9 @@ class ArbiterUrl { try { return base64Url.decode(base64Url.normalize(cert)); } on FormatException catch (error) { - throw FormatException("Invalid base64 in 'cert' query parameter: ${error.message}"); + throw FormatException( + "Invalid base64 in 'cert' query parameter: ${error.message}", + ); } } } diff --git a/useragent/lib/features/connection/auth.dart b/useragent/lib/features/connection/auth.dart index 9ae0490..f7e50a8 100644 --- a/useragent/lib/features/connection/auth.dart +++ b/useragent/lib/features/connection/auth.dart @@ -101,7 +101,9 @@ Future connectAndAuthorize( final solutionResponse = await connection.ask( UserAgentRequest( auth: ua_auth.Request( - challengeSolution: ua_auth.AuthChallengeSolution(signature: signature), + challengeSolution: ua_auth.AuthChallengeSolution( + signature: signature, + ), ), ), ); diff --git a/useragent/lib/features/connection/connection.dart b/useragent/lib/features/connection/connection.dart index 9427c83..245bb30 100644 --- a/useragent/lib/features/connection/connection.dart +++ b/useragent/lib/features/connection/connection.dart @@ -85,7 +85,9 @@ class Connection { if (response.hasId()) { final completer = _pendingRequests.remove(response.id); if (completer == null) { - talker.warning('Received response for unknown request id ${response.id}'); + talker.warning( + 'Received response for unknown request id ${response.id}', + ); return; } completer.complete(response); diff --git a/useragent/lib/features/connection/evm.dart b/useragent/lib/features/connection/evm.dart index f08fc90..0b645c0 100644 --- a/useragent/lib/features/connection/evm.dart +++ b/useragent/lib/features/connection/evm.dart @@ -9,9 +9,7 @@ Future> listEvmWallets(Connection connection) async { UserAgentRequest(evm: ua_evm.Request(walletList: Empty())), ); if (!response.hasEvm()) { - throw Exception( - 'Expected EVM response, got ${response.whichPayload()}', - ); + throw Exception('Expected EVM response, got ${response.whichPayload()}'); } final evmResponse = response.evm; @@ -37,9 +35,7 @@ Future createEvmWallet(Connection connection) async { UserAgentRequest(evm: ua_evm.Request(walletCreate: Empty())), ); if (!response.hasEvm()) { - throw Exception( - 'Expected EVM response, got ${response.whichPayload()}', - ); + throw Exception('Expected EVM response, got ${response.whichPayload()}'); } final evmResponse = response.evm; diff --git a/useragent/lib/features/connection/evm/grants.dart b/useragent/lib/features/connection/evm/grants.dart index 4e51a58..7790e83 100644 --- a/useragent/lib/features/connection/evm/grants.dart +++ b/useragent/lib/features/connection/evm/grants.dart @@ -10,9 +10,7 @@ Future> listEvmGrants(Connection connection) async { UserAgentRequest(evm: ua_evm.Request(grantList: request)), ); if (!response.hasEvm()) { - throw Exception( - 'Expected EVM response, got ${response.whichPayload()}', - ); + throw Exception('Expected EVM response, got ${response.whichPayload()}'); } final evmResponse = response.evm; @@ -50,9 +48,7 @@ Future createEvmGrant( final resp = await connection.ask(request); if (!resp.hasEvm()) { - throw Exception( - 'Expected EVM response, got ${resp.whichPayload()}', - ); + throw Exception('Expected EVM response, got ${resp.whichPayload()}'); } final evmResponse = resp.evm; @@ -70,15 +66,11 @@ Future createEvmGrant( Future deleteEvmGrant(Connection connection, int grantId) async { final response = await connection.ask( UserAgentRequest( - evm: ua_evm.Request( - grantDelete: EvmGrantDeleteRequest(grantId: grantId), - ), + evm: ua_evm.Request(grantDelete: EvmGrantDeleteRequest(grantId: grantId)), ), ); if (!response.hasEvm()) { - throw Exception( - 'Expected EVM response, got ${response.whichPayload()}', - ); + throw Exception('Expected EVM response, got ${response.whichPayload()}'); } final evmResponse = response.evm; diff --git a/useragent/lib/features/connection/evm/wallet_access.dart b/useragent/lib/features/connection/evm/wallet_access.dart index 2dcdea0..235948a 100644 --- a/useragent/lib/features/connection/evm/wallet_access.dart +++ b/useragent/lib/features/connection/evm/wallet_access.dart @@ -8,9 +8,7 @@ Future> readClientWalletAccess( required int clientId, }) async { final response = await connection.ask( - UserAgentRequest( - sdkClient: ua_sdk.Request(listWalletAccess: Empty()), - ), + UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())), ); if (!response.hasSdkClient()) { throw Exception( @@ -33,9 +31,7 @@ Future> listAllWalletAccesses( Connection connection, ) async { final response = await connection.ask( - UserAgentRequest( - sdkClient: ua_sdk.Request(listWalletAccess: Empty()), - ), + UserAgentRequest(sdkClient: ua_sdk.Request(listWalletAccess: Empty())), ); if (!response.hasSdkClient()) { throw Exception( @@ -81,9 +77,7 @@ Future writeClientWalletAccess( UserAgentRequest( sdkClient: ua_sdk.Request( revokeWalletAccess: ua_sdk.RevokeWalletAccess( - accesses: [ - for (final walletId in toRevoke) walletId, - ], + accesses: [for (final walletId in toRevoke) walletId], ), ), ), diff --git a/useragent/lib/features/connection/server_info_storage.dart b/useragent/lib/features/connection/server_info_storage.dart index d84ca52..27ed5ad 100644 --- a/useragent/lib/features/connection/server_info_storage.dart +++ b/useragent/lib/features/connection/server_info_storage.dart @@ -17,9 +17,9 @@ class StoredServerInfo { final int port; final String caCertFingerprint; - factory StoredServerInfo.fromJson(Map json) => _$StoredServerInfoFromJson(json); + factory StoredServerInfo.fromJson(Map json) => + _$StoredServerInfoFromJson(json); Map toJson() => _$StoredServerInfoToJson(this); - } abstract class ServerInfoStorage { diff --git a/useragent/lib/features/connection/vault.dart b/useragent/lib/features/connection/vault.dart index 4f02f9f..79bc6a1 100644 --- a/useragent/lib/features/connection/vault.dart +++ b/useragent/lib/features/connection/vault.dart @@ -1,5 +1,6 @@ import 'package:arbiter/features/connection/connection.dart'; -import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart' as ua_bootstrap; +import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart' + as ua_bootstrap; import 'package:arbiter/proto/user_agent/vault/unseal.pb.dart' as ua_unseal; import 'package:arbiter/proto/user_agent/vault/vault.pb.dart' as ua_vault; import 'package:arbiter/proto/user_agent.pb.dart'; @@ -27,9 +28,7 @@ Future bootstrapVault( ), ); if (!response.hasVault()) { - throw Exception( - 'Expected vault response, got ${response.whichPayload()}', - ); + throw Exception('Expected vault response, got ${response.whichPayload()}'); } final vaultResponse = response.vault; diff --git a/useragent/lib/features/identity/hazmat_mldsa.dart b/useragent/lib/features/identity/hazmat_mldsa.dart new file mode 100644 index 0000000..682a69b --- /dev/null +++ b/useragent/lib/features/identity/hazmat_mldsa.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; + +import 'package:arbiter/src/rust/api.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:arbiter/features/identity/pk_manager.dart'; + +final storage = FlutterSecureStorage( + aOptions: AndroidOptions.biometric( + enforceBiometrics: true, + biometricPromptTitle: 'Authentication Required', + ), + mOptions: MacOsOptions( + accessibility: KeychainAccessibility.unlocked_this_device, + label: "Arbiter", + description: "Confirm your identity to access vault", + synchronizable: false, + accessControlFlags: [AccessControlFlag.userPresence], + usesDataProtectionKeychain: true, + ), +); + +class HazmatMldsa extends KeyHandle { + final MldsaKey _key; + + HazmatMldsa({required MldsaKey key}) : _key = key; + + @override + Future> getPublicKey() async { + final publicKey = await _key.getPublicKey(); + return publicKey; + } + + @override + Future> sign(List data) async { + final signature = await _key.sign(message: data); + return signature; + } +} + +class HazmatMLDSAManager extends KeyManager { + static const _storageKey = "ed25519_identity"; + + @override + Future create() async { + final storedKey = await get(); + if (storedKey != null) { + return storedKey; + } + + final newKeypair = await MldsaKey.generate(); + final keyBytes = await newKeypair.toBytes(); + + await storage.write(key: _storageKey, value: base64Encode(keyBytes)); + + return HazmatMldsa(key: newKeypair); + } + + @override + Future get() async { + final storedKeyPair = await storage.read(key: _storageKey); + if (storedKeyPair == null) { + return null; + } + + final keyBytes = base64Decode(storedKeyPair); + final key = await MldsaKey.fromBytes(bytes: keyBytes); + + return HazmatMldsa(key: key); + } +} diff --git a/useragent/lib/features/identity/pk_manager.dart b/useragent/lib/features/identity/pk_manager.dart index ff6db23..d0ecb9f 100644 --- a/useragent/lib/features/identity/pk_manager.dart +++ b/useragent/lib/features/identity/pk_manager.dart @@ -1,11 +1,6 @@ -enum KeyAlgorithm { - rsa, ecdsa, ed25519 -} - -// The API to handle without storing the private key in memory. +// The API to handle without storing the private key in memory. //The implementation will use platform-specific secure storage and signing capabilities. abstract class KeyHandle { - KeyAlgorithm get alg; Future> sign(List data); Future> getPublicKey(); } diff --git a/useragent/lib/features/identity/simple_ed25519.dart b/useragent/lib/features/identity/simple_ed25519.dart deleted file mode 100644 index 01ee24d..0000000 --- a/useragent/lib/features/identity/simple_ed25519.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'dart:convert'; - -import 'package:cryptography/cryptography.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:arbiter/features/identity/pk_manager.dart'; - -final storage = FlutterSecureStorage( - aOptions: AndroidOptions.biometric( - enforceBiometrics: true, - biometricPromptTitle: 'Authentication Required', - ), - mOptions: MacOsOptions( - accessibility: KeychainAccessibility.unlocked_this_device, - label: "Arbiter", - description: "Confirm your identity to access vault", - synchronizable: false, - accessControlFlags: [ - AccessControlFlag.userPresence, - ], - usesDataProtectionKeychain: true, - ), -); - -final processor = Ed25519(); - -class SimpleEd25519 extends KeyHandle { - final SimpleKeyPair _keyPair; - - SimpleEd25519({required SimpleKeyPair keyPair}) : _keyPair = keyPair; - - @override - KeyAlgorithm get alg => KeyAlgorithm.ed25519; - - @override - Future> getPublicKey() async { - final publicKey = await _keyPair.extractPublicKey(); - return publicKey.bytes; - } - - @override - Future> sign(List data) async { - final signature = await processor.sign(data, keyPair: _keyPair); - return signature.bytes; - } -} - -class SimpleEd25519Manager extends KeyManager { - static const _storageKey = "ed25519_identity"; - static const _storagePublicKey = "ed25519_public_key"; - - @override - Future create() async { - final storedKey = await get(); - if (storedKey != null) { - return storedKey; - } - - final newKey = await processor.newKeyPair(); - final rawKey = await newKey.extract(); - - final keyData = base64Encode(rawKey.bytes); - await storage.write(key: _storageKey, value: keyData); - - final publicKeyData = base64Encode(rawKey.publicKey.bytes); - await storage.write(key: _storagePublicKey, value: publicKeyData); - - return SimpleEd25519(keyPair: newKey); - } - - @override - Future get() async { - final storedKeyPair = await storage.read(key: _storageKey); - if (storedKeyPair == null) { - return null; - } - - final publicKeyData = await storage.read(key: _storagePublicKey); - final publicKeyRaw = base64Decode(publicKeyData!); - final publicKey = SimplePublicKey( - publicKeyRaw, - type: processor.keyPairType, - ); - - final keyBytes = base64Decode(storedKeyPair); - final keypair = SimpleKeyPairData( - keyBytes, - publicKey: publicKey, - type: processor.keyPairType, - ); - - return SimpleEd25519(keyPair: keypair); - } -} diff --git a/useragent/lib/main.dart b/useragent/lib/main.dart index 5fac968..d361cdb 100644 --- a/useragent/lib/main.dart +++ b/useragent/lib/main.dart @@ -1,10 +1,12 @@ import 'package:arbiter/router.dart'; +import 'package:arbiter/src/rust/frb_generated.dart'; import 'package:flutter/material.dart' hide Router; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sizer/sizer.dart'; -void main() { +Future main() async { WidgetsFlutterBinding.ensureInitialized(); + await RustLib.init(); runApp(const ProviderScope(child: App())); } @@ -33,3 +35,5 @@ class _AppState extends State { ); } } + + diff --git a/useragent/lib/providers/connection/bootstrap_token.dart b/useragent/lib/providers/connection/bootstrap_token.dart index 48b8291..fef2bc8 100644 --- a/useragent/lib/providers/connection/bootstrap_token.dart +++ b/useragent/lib/providers/connection/bootstrap_token.dart @@ -1,5 +1,3 @@ - - import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bootstrap_token.g.dart'; diff --git a/useragent/lib/providers/evm/evm.dart b/useragent/lib/providers/evm/evm.dart index f32386f..9e73f70 100644 --- a/useragent/lib/providers/evm/evm.dart +++ b/useragent/lib/providers/evm/evm.dart @@ -2,7 +2,6 @@ import 'package:arbiter/features/connection/evm.dart' as evm; import 'package:arbiter/proto/evm.pb.dart'; import 'package:arbiter/providers/connection/connection_manager.dart'; import 'package:hooks_riverpod/experimental/mutation.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'evm.g.dart'; @@ -35,7 +34,7 @@ final createEvmWallet = Mutation(); Future executeCreateEvmWallet(MutationTarget target) async { return await createEvmWallet.run(target, (tsx) async { - final connection = await tsx.get(connectionManagerProvider.future); + final connection = await tsx.get(connectionManagerProvider.future); if (connection == null) { throw Exception('Not connected to the server.'); } @@ -44,4 +43,4 @@ Future executeCreateEvmWallet(MutationTarget target) async { await tsx.get(evmProvider.notifier).refreshWallets(); }); -} \ No newline at end of file +} diff --git a/useragent/lib/providers/key.dart b/useragent/lib/providers/key.dart index 247a906..4ad9662 100644 --- a/useragent/lib/providers/key.dart +++ b/useragent/lib/providers/key.dart @@ -1,13 +1,13 @@ import 'package:mtcore/markettakers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:arbiter/features/identity/pk_manager.dart'; -import 'package:arbiter/features/identity/simple_ed25519.dart'; +import 'package:arbiter/features/identity/hazmat_mldsa.dart'; part 'key.g.dart'; @riverpod KeyManager keyManager(Ref ref) { - return SimpleEd25519Manager(); + return HazmatMLDSAManager(); } @Riverpod(keepAlive: true) diff --git a/useragent/lib/providers/sdk_clients/list.dart b/useragent/lib/providers/sdk_clients/list.dart index a6ade66..4a52818 100644 --- a/useragent/lib/providers/sdk_clients/list.dart +++ b/useragent/lib/providers/sdk_clients/list.dart @@ -18,9 +18,7 @@ Future?> sdkClients(Ref ref) async { ); if (!resp.hasSdkClient()) { - throw Exception( - 'Expected SDK client response, got ${resp.whichPayload()}', - ); + throw Exception('Expected SDK client response, got ${resp.whichPayload()}'); } final sdkClientResponse = resp.sdkClient; if (!sdkClientResponse.hasList()) { diff --git a/useragent/lib/providers/server_info.dart b/useragent/lib/providers/server_info.dart index 27bee2e..54344c8 100644 --- a/useragent/lib/providers/server_info.dart +++ b/useragent/lib/providers/server_info.dart @@ -46,6 +46,8 @@ class ServerInfo extends _$ServerInfo { Future _fingerprint(List caCert) async { final digest = await Sha256().hash(caCert); - return digest.bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); + return digest.bytes + .map((byte) => byte.toRadixString(16).padLeft(2, '0')) + .join(); } } diff --git a/useragent/lib/screens/callouts/sdk_connect.dart b/useragent/lib/screens/callouts/sdk_connect.dart index 372261f..aa7c194 100644 --- a/useragent/lib/screens/callouts/sdk_connect.dart +++ b/useragent/lib/screens/callouts/sdk_connect.dart @@ -28,8 +28,7 @@ class SdkConnectCallout extends StatelessWidget { final hasDescription = clientInfo.hasDescription() && clientInfo.description.isNotEmpty; - final hasVersion = - clientInfo.hasVersion() && clientInfo.version.isNotEmpty; + final hasVersion = clientInfo.hasVersion() && clientInfo.version.isNotEmpty; final showInfoCard = hasDescription || hasVersion; return CreamFrame( @@ -74,10 +73,7 @@ class SdkConnectCallout extends StatelessWidget { borderRadius: BorderRadius.circular(14), border: Border.all(color: Palette.line), ), - padding: EdgeInsets.symmetric( - horizontal: 1.6.w, - vertical: 1.2.h, - ), + padding: EdgeInsets.symmetric(horizontal: 1.6.w, vertical: 1.2.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 0.6.h, diff --git a/useragent/lib/screens/dashboard.dart b/useragent/lib/screens/dashboard.dart index 4ad5390..9a8c396 100644 --- a/useragent/lib/screens/dashboard.dart +++ b/useragent/lib/screens/dashboard.dart @@ -78,7 +78,7 @@ class DashboardRouter extends StatelessWidget { } class _CalloutBell extends ConsumerWidget { - const _CalloutBell({super.key}); + const _CalloutBell(); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/useragent/lib/screens/dashboard/clients/details.dart b/useragent/lib/screens/dashboard/clients/details.dart index 3613401..25c700d 100644 --- a/useragent/lib/screens/dashboard/clients/details.dart +++ b/useragent/lib/screens/dashboard/clients/details.dart @@ -1,4 +1,3 @@ - import 'package:arbiter/proto/user_agent/sdk_client.pb.dart' as ua_sdk; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; @@ -13,5 +12,4 @@ class ClientDetails extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { throw UnimplementedError(); } - } diff --git a/useragent/lib/screens/dashboard/clients/details/widgets/client_summary_card.dart b/useragent/lib/screens/dashboard/clients/details/widgets/client_summary_card.dart index 81cc528..4de0b5d 100644 --- a/useragent/lib/screens/dashboard/clients/details/widgets/client_summary_card.dart +++ b/useragent/lib/screens/dashboard/clients/details/widgets/client_summary_card.dart @@ -12,30 +12,24 @@ class ClientSummaryCard extends StatelessWidget { return CreamFrame( padding: const EdgeInsets.all(20), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - client.info.name, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text(client.info.description), - const SizedBox(height: 16), - Wrap( - runSpacing: 8, - spacing: 16, - children: [ - _Fact(label: 'Client ID', value: '${client.id}'), - _Fact(label: 'Version', value: client.info.version), - _Fact( - label: 'Registered', - value: _formatDate(client.createdAt), - ), - _Fact(label: 'Pubkey', value: _shortPubkey(client.pubkey)), - ], - ), - ], - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(client.info.name, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text(client.info.description), + const SizedBox(height: 16), + Wrap( + runSpacing: 8, + spacing: 16, + children: [ + _Fact(label: 'Client ID', value: '${client.id}'), + _Fact(label: 'Version', value: client.info.version), + _Fact(label: 'Registered', value: _formatDate(client.createdAt)), + _Fact(label: 'Pubkey', value: _shortPubkey(client.pubkey)), + ], + ), + ], + ), ); } } diff --git a/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_save_bar.dart b/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_save_bar.dart index b96f2f6..d52828a 100644 --- a/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_save_bar.dart +++ b/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_save_bar.dart @@ -28,27 +28,27 @@ class WalletAccessSaveBar extends StatelessWidget { return CreamFrame( padding: const EdgeInsets.all(16), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (errorText != null) ...[ - Text(errorText, style: TextStyle(color: Palette.coral)), - const SizedBox(height: 12), - ], - Row( - children: [ - TextButton( - onPressed: state.hasChanges && !isPending ? onDiscard : null, - child: const Text('Reset'), - ), - const Spacer(), - FilledButton( - onPressed: state.hasChanges && !isPending ? onSave : null, - child: Text(isPending ? 'Saving...' : 'Save changes'), - ), - ], - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (errorText != null) ...[ + Text(errorText, style: TextStyle(color: Palette.coral)), + const SizedBox(height: 12), ], - ), + Row( + children: [ + TextButton( + onPressed: state.hasChanges && !isPending ? onDiscard : null, + child: const Text('Reset'), + ), + const Spacer(), + FilledButton( + onPressed: state.hasChanges && !isPending ? onSave : null, + child: Text(isPending ? 'Saving...' : 'Save changes'), + ), + ], + ), + ], + ), ); } } diff --git a/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_section.dart b/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_section.dart index cf55b29..54f348e 100644 --- a/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_section.dart +++ b/useragent/lib/screens/dashboard/clients/details/widgets/wallet_access_section.dart @@ -30,26 +30,23 @@ class WalletAccessSection extends ConsumerWidget { return CreamFrame( padding: const EdgeInsets.all(20), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Wallet access', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text('Choose which managed wallets this client can see.'), - const SizedBox(height: 16), - _WalletAccessBody( - clientId: clientId, - state: state, - accessSelectionAsync: accessSelectionAsync, - isSavePending: isSavePending, - optionsAsync: optionsAsync, - onSearchChanged: onSearchChanged, - onToggleWallet: onToggleWallet, - ), - ], - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Wallet access', style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text('Choose which managed wallets this client can see.'), + const SizedBox(height: 16), + _WalletAccessBody( + clientId: clientId, + state: state, + accessSelectionAsync: accessSelectionAsync, + isSavePending: isSavePending, + optionsAsync: optionsAsync, + onSearchChanged: onSearchChanged, + onToggleWallet: onToggleWallet, + ), + ], + ), ); } } diff --git a/useragent/lib/screens/dashboard/clients/table.dart b/useragent/lib/screens/dashboard/clients/table.dart index 9ccbcda..3d9a452 100644 --- a/useragent/lib/screens/dashboard/clients/table.dart +++ b/useragent/lib/screens/dashboard/clients/table.dart @@ -378,48 +378,48 @@ class _ClientTable extends StatelessWidget { builder: (context, constraints) { final tableWidth = math.max(_tableMinWidth, constraints.maxWidth); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Registered clients', - style: theme.textTheme.titleLarge?.copyWith( - color: Palette.ink, - fontWeight: FontWeight.w800, - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Registered clients', + style: theme.textTheme.titleLarge?.copyWith( + color: Palette.ink, + fontWeight: FontWeight.w800, ), - SizedBox(height: 0.6.h), - Text( - 'Every entry here has authenticated with Arbiter at least once.', - style: theme.textTheme.bodyMedium?.copyWith( - color: Palette.ink.withValues(alpha: 0.70), - height: 1.4, - ), + ), + SizedBox(height: 0.6.h), + Text( + 'Every entry here has authenticated with Arbiter at least once.', + style: theme.textTheme.bodyMedium?.copyWith( + color: Palette.ink.withValues(alpha: 0.70), + height: 1.4, ), - SizedBox(height: 1.6.h), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: tableWidth, - child: Column( - children: [ - const _ClientTableHeader(), - SizedBox(height: 1.h), - for (var i = 0; i < clients.length; i++) - Padding( - padding: EdgeInsets.only( - bottom: i == clients.length - 1 ? 0 : 1.h, - ), - child: _ClientTableRow(client: clients[i]), + ), + SizedBox(height: 1.6.h), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + children: [ + const _ClientTableHeader(), + SizedBox(height: 1.h), + for (var i = 0; i < clients.length; i++) + Padding( + padding: EdgeInsets.only( + bottom: i == clients.length - 1 ? 0 : 1.h, ), - ], - ), + child: _ClientTableRow(client: clients[i]), + ), + ], ), ), - ], - ); - }, - ), + ), + ], + ); + }, + ), ); } } diff --git a/useragent/lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart b/useragent/lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart index cfee2fe..6be82e5 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart @@ -31,7 +31,9 @@ class ClientPickerField extends ConsumerWidget { ? null : (value) { ref.read(grantCreationProvider.notifier).setClientId(value); - FormBuilder.of(context)?.fields['walletAccessId']?.didChange(null); + FormBuilder.of( + context, + )?.fields['walletAccessId']?.didChange(null); }, ); } diff --git a/useragent/lib/screens/dashboard/evm/grants/create/fields/date_time_field.dart b/useragent/lib/screens/dashboard/evm/grants/create/fields/date_time_field.dart index 166359c..62d3ddb 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/fields/date_time_field.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/fields/date_time_field.dart @@ -16,46 +16,48 @@ class FormBuilderDateTimeField extends FormBuilderField { super.onChanged, super.validator, }) : super( - builder: (FormFieldState field) { - final value = field.value; - return OutlinedButton( - onPressed: () async { - final ctx = field.context; - final now = DateTime.now(); - final date = await showDatePicker( - context: ctx, - firstDate: DateTime(now.year - 5), - lastDate: DateTime(now.year + 10), - initialDate: value ?? now, - ); - if (date == null) return; - if (!ctx.mounted) return; - final time = await showTimePicker( - context: ctx, - initialTime: TimeOfDay.fromDateTime(value ?? now), - ); - if (time == null) return; - field.didChange(DateTime( - date.year, - date.month, - date.day, - time.hour, - time.minute, - )); - }, - onLongPress: value == null ? null : () => field.didChange(null), - child: Padding( - padding: EdgeInsets.symmetric(vertical: 1.8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label), - SizedBox(height: 0.6.h), - Text(value?.toLocal().toString() ?? 'Not set'), - ], - ), - ), - ); - }, - ); + builder: (FormFieldState field) { + final value = field.value; + return OutlinedButton( + onPressed: () async { + final ctx = field.context; + final now = DateTime.now(); + final date = await showDatePicker( + context: ctx, + firstDate: DateTime(now.year - 5), + lastDate: DateTime(now.year + 10), + initialDate: value ?? now, + ); + if (date == null) return; + if (!ctx.mounted) return; + final time = await showTimePicker( + context: ctx, + initialTime: TimeOfDay.fromDateTime(value ?? now), + ); + if (time == null) return; + field.didChange( + DateTime( + date.year, + date.month, + date.day, + time.hour, + time.minute, + ), + ); + }, + onLongPress: value == null ? null : () => field.didChange(null), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 1.8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label), + SizedBox(height: 0.6.h), + Text(value?.toLocal().toString() ?? 'Not set'), + ], + ), + ), + ); + }, + ); } diff --git a/useragent/lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart b/useragent/lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart index 72a760a..cde8d17 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart @@ -36,8 +36,8 @@ class WalletAccessPickerField extends ConsumerWidget { helperText: state.selectedClientId == null ? 'Select a client first' : accesses.isEmpty - ? 'No wallet accesses for this client' - : null, + ? 'No wallet accesses for this client' + : null, border: const OutlineInputBorder(), ), items: [ diff --git a/useragent/lib/screens/dashboard/evm/grants/create/grants/ether_transfer_grant.dart b/useragent/lib/screens/dashboard/evm/grants/create/grants/ether_transfer_grant.dart index 547e247..bc7bda6 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/grants/ether_transfer_grant.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/grants/ether_transfer_grant.dart @@ -93,9 +93,9 @@ class _EtherTransferForm extends ConsumerWidget { SizedBox(height: 1.6.h), Text( 'Ether volume limit', - style: Theme.of(context).textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w800, - ), + style: Theme.of( + context, + ).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800), ), SizedBox(height: 0.8.h), Row( @@ -157,9 +157,9 @@ class _EtherTargetsField extends StatelessWidget { Expanded( child: Text( 'Ether targets', - style: Theme.of(context).textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w800, - ), + style: Theme.of( + context, + ).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800), ), ), TextButton.icon( diff --git a/useragent/lib/screens/dashboard/evm/grants/create/grants/token_transfer_grant.dart b/useragent/lib/screens/dashboard/evm/grants/create/grants/token_transfer_grant.dart index 9352b94..58e27de 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/grants/token_transfer_grant.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/grants/token_transfer_grant.dart @@ -13,7 +13,11 @@ import 'package:sizer/sizer.dart'; part 'token_transfer_grant.g.dart'; class VolumeLimitEntry { - VolumeLimitEntry({required this.id, this.amount = '', this.windowSeconds = ''}); + VolumeLimitEntry({ + required this.id, + this.amount = '', + this.windowSeconds = '', + }); final int id; final String amount; @@ -27,7 +31,6 @@ class VolumeLimitEntry { ); } - @riverpod class TokenGrantLimits extends _$TokenGrantLimits { int _nextId = 0; @@ -47,7 +50,6 @@ class TokenGrantLimits extends _$TokenGrantLimits { void remove(int index) => state = [...state]..removeAt(index); } - class TokenTransferGrantHandler implements GrantFormHandler { const TokenTransferGrantHandler(); @@ -65,11 +67,16 @@ class TokenTransferGrantHandler implements GrantFormHandler { return SpecificGrant( tokenTransfer: TokenTransferSettings( - tokenContract: - parseHexAddress(formValues['tokenContract'] as String? ?? ''), + tokenContract: parseHexAddress( + formValues['tokenContract'] as String? ?? '', + ), target: targetText.trim().isEmpty ? null : parseHexAddress(targetText), volumeLimits: limits - .where((e) => e.amount.trim().isNotEmpty && e.windowSeconds.trim().isNotEmpty) + .where( + (e) => + e.amount.trim().isNotEmpty && + e.windowSeconds.trim().isNotEmpty, + ) .map( (e) => VolumeRateLimit( maxVolume: parseBigIntBytes(e.amount), @@ -153,9 +160,9 @@ class _TokenVolumeLimitsField extends StatelessWidget { Expanded( child: Text( 'Token volume limits', - style: Theme.of(context).textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w800, - ), + style: Theme.of( + context, + ).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800), ), ), TextButton.icon( @@ -196,7 +203,9 @@ class _TokenVolumeLimitRow extends HookWidget { @override Widget build(BuildContext context) { final amountController = useTextEditingController(text: value.amount); - final windowController = useTextEditingController(text: value.windowSeconds); + final windowController = useTextEditingController( + text: value.windowSeconds, + ); return Row( children: [ @@ -214,8 +223,7 @@ class _TokenVolumeLimitRow extends HookWidget { Expanded( child: TextField( controller: windowController, - onChanged: (next) => - onChanged(value.copyWith(windowSeconds: next)), + onChanged: (next) => onChanged(value.copyWith(windowSeconds: next)), decoration: const InputDecoration( labelText: 'Window (seconds)', border: OutlineInputBorder(), diff --git a/useragent/lib/screens/dashboard/evm/grants/create/screen.dart b/useragent/lib/screens/dashboard/evm/grants/create/screen.dart index b813c14..8808e3e 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/screen.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/screen.dart @@ -25,10 +25,10 @@ const _etherHandler = EtherTransferGrantHandler(); const _tokenHandler = TokenTransferGrantHandler(); GrantFormHandler _handlerFor(SpecificGrant_Grant type) => switch (type) { - SpecificGrant_Grant.etherTransfer => _etherHandler, - SpecificGrant_Grant.tokenTransfer => _tokenHandler, - _ => throw ArgumentError('Unsupported grant type: $type'), - }; + SpecificGrant_Grant.etherTransfer => _etherHandler, + SpecificGrant_Grant.tokenTransfer => _tokenHandler, + _ => throw ArgumentError('Unsupported grant type: $type'), +}; @RoutePage() class CreateEvmGrantScreen extends HookConsumerWidget { @@ -62,12 +62,14 @@ class CreateEvmGrantScreen extends HookConsumerWidget { ); final validFrom = formValues['validFrom'] as DateTime?; final validUntil = formValues['validUntil'] as DateTime?; - if (validFrom != null) sharedSettings.validFrom = toTimestamp(validFrom); + if (validFrom != null) + sharedSettings.validFrom = toTimestamp(validFrom); if (validUntil != null) { sharedSettings.validUntil = toTimestamp(validUntil); } - final gasBytes = - optionalBigIntBytes(formValues['maxGasFeePerGas'] as String? ?? ''); + final gasBytes = optionalBigIntBytes( + formValues['maxGasFeePerGas'] as String? ?? '', + ); if (gasBytes != null) sharedSettings.maxGasFeePerGas = gasBytes; final priorityBytes = optionalBigIntBytes( formValues['maxPriorityFeePerGas'] as String? ?? '', @@ -106,7 +108,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { SizedBox(height: 1.8.h), const _Section( title: 'Authorization', - tooltip: 'Select which SDK client receives this grant and ' + tooltip: + 'Select which SDK client receives this grant and ' 'which of its wallet accesses it applies to.', child: AuthorizationFields(), ), @@ -118,7 +121,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { const Expanded( child: _Section( title: 'Chain', - tooltip: 'Restrict this grant to a specific EVM chain ID. ' + tooltip: + 'Restrict this grant to a specific EVM chain ID. ' 'Leave empty to allow any chain.', optional: true, child: ChainIdField(), @@ -128,7 +132,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { const Expanded( child: _Section( title: 'Timing', - tooltip: 'Set an optional validity window. ' + tooltip: + 'Set an optional validity window. ' 'Signing requests outside this period will be rejected.', optional: true, child: ValidityWindowField(), @@ -145,7 +150,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { const Expanded( child: _Section( title: 'Gas limits', - tooltip: 'Cap the gas fees this grant may authorize. ' + tooltip: + 'Cap the gas fees this grant may authorize. ' 'Transactions exceeding these values will be rejected.', optional: true, child: GasFeeOptionsField(), @@ -155,7 +161,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { const Expanded( child: _Section( title: 'Transaction limits', - tooltip: 'Limit how many transactions can be signed ' + tooltip: + 'Limit how many transactions can be signed ' 'within a rolling time window.', optional: true, child: TransactionRateLimitField(), @@ -172,7 +179,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget { SizedBox(height: 1.8.h), _Section( title: 'Grant-specific options', - tooltip: 'Rules specific to the selected transfer type. ' + tooltip: + 'Rules specific to the selected transfer type. ' 'Switch between Ether and token above to change these fields.', child: handler.buildForm(context, ref), ), @@ -180,8 +188,7 @@ class CreateEvmGrantScreen extends HookConsumerWidget { Align( alignment: Alignment.centerRight, child: FilledButton.icon( - onPressed: - createMutation is MutationPending ? null : submit, + onPressed: createMutation is MutationPending ? null : submit, icon: createMutation is MutationPending ? SizedBox( width: 1.8.h, @@ -266,9 +273,9 @@ class _Section extends StatelessWidget { children: [ Text( title, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w800, - ), + style: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800), ), SizedBox(width: 0.4.w), Tooltip( @@ -283,9 +290,9 @@ class _Section extends StatelessWidget { SizedBox(width: 0.6.w), Text( '(optional)', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: subtleColor, - ), + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith(color: subtleColor), ), ], ], diff --git a/useragent/lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart b/useragent/lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart index 9722d05..65aba62 100644 --- a/useragent/lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart +++ b/useragent/lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart @@ -19,4 +19,3 @@ class AuthorizationFields extends StatelessWidget { ); } } - diff --git a/useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart b/useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart index c04e4d3..76857fd 100644 --- a/useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart +++ b/useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart @@ -46,9 +46,7 @@ class GrantCard extends ConsumerWidget { final accessById = { for (final a in walletAccesses) a.id: a, }; - final walletById = { - for (final w in wallets) w.id: w, - }; + final walletById = {for (final w in wallets) w.id: w}; final clientNameById = { for (final c in clients) c.id: c.info.name, }; @@ -192,8 +190,9 @@ class GrantCard extends ConsumerWidget { padding: EdgeInsets.symmetric(horizontal: 0.8.w), child: Text( '·', - style: theme.textTheme.bodySmall - ?.copyWith(color: muted), + style: theme.textTheme.bodySmall?.copyWith( + color: muted, + ), ), ), Expanded( @@ -201,8 +200,9 @@ class GrantCard extends ConsumerWidget { clientLabel, maxLines: 1, overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodySmall - ?.copyWith(color: muted), + style: theme.textTheme.bodySmall?.copyWith( + color: muted, + ), ), ), ], diff --git a/useragent/lib/screens/dashboard/evm/wallets/header.dart b/useragent/lib/screens/dashboard/evm/wallets/header.dart index 646d5ea..b74a50a 100644 --- a/useragent/lib/screens/dashboard/evm/wallets/header.dart +++ b/useragent/lib/screens/dashboard/evm/wallets/header.dart @@ -5,7 +5,6 @@ import 'package:hooks_riverpod/experimental/mutation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sizer/sizer.dart'; - class CreateWalletButton extends ConsumerWidget { const CreateWalletButton({super.key}); @@ -88,7 +87,6 @@ class RefreshWalletButton extends ConsumerWidget { } } - String _formatError(Object error) { final message = error.toString(); if (message.startsWith('Exception: ')) { diff --git a/useragent/lib/screens/dashboard/evm/wallets/table.dart b/useragent/lib/screens/dashboard/evm/wallets/table.dart index a364d72..6d850e1 100644 --- a/useragent/lib/screens/dashboard/evm/wallets/table.dart +++ b/useragent/lib/screens/dashboard/evm/wallets/table.dart @@ -36,54 +36,51 @@ class WalletTable extends StatelessWidget { return CreamFrame( padding: EdgeInsets.all(2.h), child: LayoutBuilder( - builder: (context, constraints) { - final tableWidth = math.max(_tableMinWidth, constraints.maxWidth); + builder: (context, constraints) { + final tableWidth = math.max(_tableMinWidth, constraints.maxWidth); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Managed wallets', - style: theme.textTheme.titleLarge?.copyWith( - color: Palette.ink, - fontWeight: FontWeight.w800, - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Managed wallets', + style: theme.textTheme.titleLarge?.copyWith( + color: Palette.ink, + fontWeight: FontWeight.w800, ), - SizedBox(height: 0.6.h), - Text( - 'Every address here is generated and held by Arbiter.', - style: theme.textTheme.bodyMedium?.copyWith( - color: Palette.ink.withValues(alpha: 0.70), - height: 1.4, - ), + ), + SizedBox(height: 0.6.h), + Text( + 'Every address here is generated and held by Arbiter.', + style: theme.textTheme.bodyMedium?.copyWith( + color: Palette.ink.withValues(alpha: 0.70), + height: 1.4, ), - SizedBox(height: 1.6.h), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SizedBox( - width: tableWidth, - child: Column( - children: [ - const _WalletTableHeader(), - SizedBox(height: 1.h), - for (var i = 0; i < wallets.length; i++) - Padding( - padding: EdgeInsets.only( - bottom: i == wallets.length - 1 ? 0 : 1.h, - ), - child: _WalletTableRow( - wallet: wallets[i], - index: i, - ), + ), + SizedBox(height: 1.6.h), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: tableWidth, + child: Column( + children: [ + const _WalletTableHeader(), + SizedBox(height: 1.h), + for (var i = 0; i < wallets.length; i++) + Padding( + padding: EdgeInsets.only( + bottom: i == wallets.length - 1 ? 0 : 1.h, ), - ], - ), + child: _WalletTableRow(wallet: wallets[i], index: i), + ), + ], ), ), - ], - ); - }, - ), + ), + ], + ); + }, + ), ); } } diff --git a/useragent/lib/src/rust/api.dart b/useragent/lib/src/rust/api.dart new file mode 100644 index 0000000..0c22fbf --- /dev/null +++ b/useragent/lib/src/rust/api.dart @@ -0,0 +1,22 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +// Rust type: RustOpaqueMoi> +abstract class MldsaKey implements RustOpaqueInterface { + static Future fromBytes({required List bytes}) => + RustLib.instance.api.crateApiMldsaKeyFromBytes(bytes: bytes); + + static Future generate() => + RustLib.instance.api.crateApiMldsaKeyGenerate(); + + Future getPublicKey(); + + Future sign({required List message}); + + Future toBytes(); +} diff --git a/useragent/lib/src/rust/frb_generated.dart b/useragent/lib/src/rust/frb_generated.dart new file mode 100644 index 0000000..d9206e5 --- /dev/null +++ b/useragent/lib/src/rust/frb_generated.dart @@ -0,0 +1,573 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'frb_generated.io.dart' + if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +/// Main entrypoint of the Rust API +class RustLib extends BaseEntrypoint { + @internal + static final instance = RustLib._(); + + RustLib._(); + + /// Initialize flutter_rust_bridge + static Future init({ + RustLibApi? api, + BaseHandler? handler, + ExternalLibrary? externalLibrary, + bool forceSameCodegenVersion = true, + }) async { + await instance.initImpl( + api: api, + handler: handler, + externalLibrary: externalLibrary, + forceSameCodegenVersion: forceSameCodegenVersion, + ); + } + + /// Initialize flutter_rust_bridge in mock mode. + /// No libraries for FFI are loaded. + static void initMock({required RustLibApi api}) { + instance.initMockImpl(api: api); + } + + /// Dispose flutter_rust_bridge + /// + /// The call to this function is optional, since flutter_rust_bridge (and everything else) + /// is automatically disposed when the app stops. + static void dispose() => instance.disposeImpl(); + + @override + ApiImplConstructor get apiImplConstructor => + RustLibApiImpl.new; + + @override + WireConstructor get wireConstructor => + RustLibWire.fromExternalLibrary; + + @override + Future executeRustInitializers() async {} + + @override + ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => + kDefaultExternalLibraryLoaderConfig; + + @override + String get codegenVersion => '2.12.0'; + + @override + int get rustContentHash => -437661335; + + static const kDefaultExternalLibraryLoaderConfig = + ExternalLibraryLoaderConfig( + stem: 'rust_lib_arbiter', + ioDirectory: 'rust/target/release/', + webPrefix: 'pkg/', + wasmBindgenName: 'wasm_bindgen', + ); +} + +abstract class RustLibApi extends BaseApi { + Future crateApiMldsaKeyFromBytes({required List bytes}); + + Future crateApiMldsaKeyGenerate(); + + Future crateApiMldsaKeyGetPublicKey({required MldsaKey that}); + + Future crateApiMldsaKeySign({ + required MldsaKey that, + required List message, + }); + + Future crateApiMldsaKeyToBytes({required MldsaKey that}); + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_MldsaKey; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_MldsaKey; + + CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_MldsaKeyPtr; +} + +class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { + RustLibApiImpl({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @override + Future crateApiMldsaKeyFromBytes({required List bytes}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(bytes, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 1, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiMldsaKeyFromBytesConstMeta, + argValues: [bytes], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMldsaKeyFromBytesConstMeta => const TaskConstMeta( + debugName: "MldsaKey_from_bytes", + argNames: ["bytes"], + ); + + @override + Future crateApiMldsaKeyGenerate() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 2, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey, + decodeErrorData: null, + ), + constMeta: kCrateApiMldsaKeyGenerateConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMldsaKeyGenerateConstMeta => + const TaskConstMeta(debugName: "MldsaKey_generate", argNames: []); + + @override + Future crateApiMldsaKeyGetPublicKey({required MldsaKey that}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + that, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 3, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: null, + ), + constMeta: kCrateApiMldsaKeyGetPublicKeyConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMldsaKeyGetPublicKeyConstMeta => + const TaskConstMeta( + debugName: "MldsaKey_get_public_key", + argNames: ["that"], + ); + + @override + Future crateApiMldsaKeySign({ + required MldsaKey that, + required List message, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + that, + serializer, + ); + sse_encode_list_prim_u_8_loose(message, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 4, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateApiMldsaKeySignConstMeta, + argValues: [that, message], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMldsaKeySignConstMeta => const TaskConstMeta( + debugName: "MldsaKey_sign", + argNames: ["that", "message"], + ); + + @override + Future crateApiMldsaKeyToBytes({required MldsaKey that}) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + that, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 5, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: null, + ), + constMeta: kCrateApiMldsaKeyToBytesConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiMldsaKeyToBytesConstMeta => + const TaskConstMeta(debugName: "MldsaKey_to_bytes", argNames: ["that"]); + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_MldsaKey => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_MldsaKey => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnyhowException(raw as String); + } + + @protected + MldsaKey + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MldsaKeyImpl.frbInternalDcoDecode(raw as List); + } + + @protected + MldsaKey + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MldsaKeyImpl.frbInternalDcoDecode(raw as List); + } + + @protected + MldsaKey + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MldsaKeyImpl.frbInternalDcoDecode(raw as List); + } + + @protected + String dco_decode_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as String; + } + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as List; + } + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as Uint8List; + } + + @protected + int dco_decode_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + void dco_decode_unit(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return; + } + + @protected + BigInt dco_decode_usize(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_String(deserializer); + return AnyhowException(inner); + } + + @protected + MldsaKey + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MldsaKeyImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + MldsaKey + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MldsaKeyImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + MldsaKey + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MldsaKeyImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + String sse_decode_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return utf8.decoder.convert(inner); + } + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + int sse_decode_u_8(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8(); + } + + @protected + void sse_decode_unit(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.message, serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MldsaKeyImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MldsaKeyImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MldsaKeyImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void sse_encode_String(String self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); + } + + @protected + void sse_encode_list_prim_u_8_loose( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List( + self is Uint8List ? self : Uint8List.fromList(self), + ); + } + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List(self); + } + + @protected + void sse_encode_u_8(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self); + } + + @protected + void sse_encode_unit(void self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } +} + +@sealed +class MldsaKeyImpl extends RustOpaque implements MldsaKey { + // Not to be used by end users + MldsaKeyImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + MldsaKeyImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_MldsaKey, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_MldsaKey, + rustArcDecrementStrongCountPtr: + RustLib.instance.api.rust_arc_decrement_strong_count_MldsaKeyPtr, + ); + + Future getPublicKey() => + RustLib.instance.api.crateApiMldsaKeyGetPublicKey(that: this); + + Future sign({required List message}) => + RustLib.instance.api.crateApiMldsaKeySign(that: this, message: message); + + Future toBytes() => + RustLib.instance.api.crateApiMldsaKeyToBytes(that: this); +} diff --git a/useragent/lib/src/rust/frb_generated.io.dart b/useragent/lib/src/rust/frb_generated.io.dart new file mode 100644 index 0000000..942e9cd --- /dev/null +++ b/useragent/lib/src/rust/frb_generated.io.dart @@ -0,0 +1,211 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'dart:ffi' as ffi; +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_MldsaKeyPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKeyPtr; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + MldsaKey + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + MldsaKey + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + MldsaKey + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + String dco_decode_String(dynamic raw); + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + MldsaKey + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + MldsaKey + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + MldsaKey + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) => + RustLibWire(lib.ffiDynamicLibrary); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + RustLibWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKeyPtr = + _lookup)>>( + 'frbgen_arbiter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKeyPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKeyPtr = + _lookup)>>( + 'frbgen_arbiter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKeyPtr + .asFunction)>(); +} diff --git a/useragent/lib/src/rust/frb_generated.web.dart b/useragent/lib/src/rust/frb_generated.web.dart new file mode 100644 index 0000000..640f1f8 --- /dev/null +++ b/useragent/lib/src/rust/frb_generated.web.dart @@ -0,0 +1,203 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +// Static analysis wrongly picks the IO variant, thus ignore this +// ignore_for_file: argument_type_not_assignable + +import 'api.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_MldsaKeyPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + MldsaKey + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + MldsaKey + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + MldsaKey + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + dynamic raw, + ); + + @protected + String dco_decode_String(dynamic raw); + + @protected + List dco_decode_list_prim_u_8_loose(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + MldsaKey + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + MldsaKey + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + MldsaKey + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + SseDeserializer deserializer, + ); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + MldsaKey self, + SseSerializer serializer, + ); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + RustLibWire.fromExternalLibrary(ExternalLibrary lib); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr, + ); +} + +@JS('wasm_bindgen') +external RustLibWasmModule get wasmModule; + +@JS() +@anonymous +extension type RustLibWasmModule._(JSObject _) implements JSObject { + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + int ptr, + ); +} diff --git a/useragent/lib/widgets/bottom_popup.dart b/useragent/lib/widgets/bottom_popup.dart index 875ef08..3e95db0 100644 --- a/useragent/lib/widgets/bottom_popup.dart +++ b/useragent/lib/widgets/bottom_popup.dart @@ -50,7 +50,9 @@ class _BottomPopupRoute extends StatelessWidget { Positioned.fill( child: GestureDetector( behavior: HitTestBehavior.opaque, - onTap: barrierDismissible ? () => Navigator.of(context).pop() : null, + onTap: barrierDismissible + ? () => Navigator.of(context).pop() + : null, child: AnimatedBuilder( animation: barrierAnimation, builder: (context, child) { @@ -71,11 +73,10 @@ class _BottomPopupRoute extends StatelessWidget { child: FadeTransition( opacity: popupAnimation, child: SlideTransition( - position: - Tween( - begin: const Offset(0, 0.08), - end: Offset.zero, - ).animate(popupAnimation), + position: Tween( + begin: const Offset(0, 0.08), + end: Offset.zero, + ).animate(popupAnimation), child: GestureDetector( onTap: () {}, child: Builder(builder: builder), diff --git a/useragent/lib/widgets/state_panel.dart b/useragent/lib/widgets/state_panel.dart index 4c73875..ca00b06 100644 --- a/useragent/lib/widgets/state_panel.dart +++ b/useragent/lib/widgets/state_panel.dart @@ -28,42 +28,42 @@ class StatePanel extends StatelessWidget { return CreamFrame( padding: EdgeInsets.all(2.8.h), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (busy) - SizedBox( - width: 2.8.h, - height: 2.8.h, - child: const CircularProgressIndicator(strokeWidth: 2.5), - ) - else - Icon(icon, size: 34, color: Palette.coral), - SizedBox(height: 1.8.h), - Text( - title, - style: theme.textTheme.headlineSmall?.copyWith( - color: Palette.ink, - fontWeight: FontWeight.w800, - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (busy) + SizedBox( + width: 2.8.h, + height: 2.8.h, + child: const CircularProgressIndicator(strokeWidth: 2.5), + ) + else + Icon(icon, size: 34, color: Palette.coral), + SizedBox(height: 1.8.h), + Text( + title, + style: theme.textTheme.headlineSmall?.copyWith( + color: Palette.ink, + fontWeight: FontWeight.w800, ), - SizedBox(height: 1.h), - Text( - body, - style: theme.textTheme.bodyLarge?.copyWith( - color: Palette.ink.withValues(alpha: 0.72), - height: 1.5, - ), + ), + SizedBox(height: 1.h), + Text( + body, + style: theme.textTheme.bodyLarge?.copyWith( + color: Palette.ink.withValues(alpha: 0.72), + height: 1.5, + ), + ), + if (actionLabel != null && onAction != null) ...[ + SizedBox(height: 2.h), + OutlinedButton.icon( + onPressed: () => onAction!(), + icon: const Icon(Icons.refresh), + label: Text(actionLabel!), ), - if (actionLabel != null && onAction != null) ...[ - SizedBox(height: 2.h), - OutlinedButton.icon( - onPressed: () => onAction!(), - icon: const Icon(Icons.refresh), - label: Text(actionLabel!), - ), - ], ], - ), + ], + ), ); } } diff --git a/useragent/macos/Podfile.lock b/useragent/macos/Podfile.lock index 2dad058..26d37b4 100644 --- a/useragent/macos/Podfile.lock +++ b/useragent/macos/Podfile.lock @@ -9,6 +9,8 @@ PODS: - FlutterMacOS (1.0.0) - rive_native (0.0.1): - FlutterMacOS + - rust_lib_arbiter (0.0.1): + - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS @@ -18,6 +20,7 @@ DEPENDENCIES: - flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - FlutterMacOS (from `Flutter/ephemeral`) - rive_native (from `Flutter/ephemeral/.symlinks/plugins/rive_native/macos`) + - rust_lib_arbiter (from `Flutter/ephemeral/.symlinks/plugins/rust_lib_arbiter/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) EXTERNAL SOURCES: @@ -31,6 +34,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral rive_native: :path: Flutter/ephemeral/.symlinks/plugins/rive_native/macos + rust_lib_arbiter: + :path: Flutter/ephemeral/.symlinks/plugins/rust_lib_arbiter/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos @@ -40,6 +45,7 @@ SPEC CHECKSUMS: flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 rive_native: 1c53d33e44c2b54424810effea4590671dd220c7 + rust_lib_arbiter: 78dcf27cf17e741c6f4f0b12b64a40980746698a share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc PODFILE CHECKSUM: 224cb1c0d6f5312abfc2477bcb5c7f1fca2574fb diff --git a/useragent/pubspec.lock b/useragent/pubspec.lock index a051ec2..ba8a18d 100644 --- a/useragent/pubspec.lock +++ b/useragent/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.5" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" build_config: dependency: transitive description: @@ -218,7 +226,7 @@ packages: source: hosted version: "0.3.5+2" crypto: - dependency: "direct main" + dependency: transitive description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf @@ -226,7 +234,7 @@ packages: source: hosted version: "3.0.7" cryptography: - dependency: "direct main" + dependency: transitive description: name: cryptography sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" @@ -311,6 +319,11 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_form_builder: dependency: "direct main" description: @@ -343,6 +356,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + flutter_rust_bridge: + dependency: "direct main" + description: + name: flutter_rust_bridge + sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a + url: "https://pub.dev" + source: hosted + version: "2.12.0" flutter_secure_storage: dependency: "direct main" description: @@ -433,6 +454,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -537,6 +563,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: transitive description: @@ -801,6 +832,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.dev" + source: hosted + version: "5.0.5" protobuf: dependency: "direct main" description: @@ -881,6 +920,13 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0+1" + rust_lib_arbiter: + dependency: "direct main" + description: + path: rust_builder + relative: true + source: path + version: "0.0.1" share_plus: dependency: transitive description: @@ -1022,6 +1068,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" talker: dependency: "direct main" description: @@ -1182,6 +1236,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: diff --git a/useragent/pubspec.yaml b/useragent/pubspec.yaml index 9a77966..eb26c37 100644 --- a/useragent/pubspec.yaml +++ b/useragent/pubspec.yaml @@ -21,8 +21,6 @@ dependencies: mtcore: hosted: https://git.markettakers.org/api/packages/MarketTakers/pub/ version: ^1.0.6 - cryptography: ^2.9.0 - crypto: ^3.0.6 flutter_secure_storage: ^10.0.0 cryptography_flutter: ^2.3.4 riverpod_annotation: ^4.0.0 @@ -35,6 +33,9 @@ dependencies: json_annotation: ^4.9.0 timeago: ^3.7.1 flutter_form_builder: ^10.3.0+2 + rust_lib_arbiter: + path: rust_builder + flutter_rust_bridge: 2.12.0 dev_dependencies: flutter_test: @@ -46,6 +47,8 @@ dev_dependencies: auto_route_generator: ^10.4.0 freezed: ^3.2.3 json_serializable: ^6.11.2 + integration_test: + sdk: flutter flutter: uses-material-design: true diff --git a/useragent/rust/.gitignore b/useragent/rust/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/useragent/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/useragent/rust/Cargo.lock b/useragent/rust/Cargo.lock new file mode 100644 index 0000000..020b2fc --- /dev/null +++ b/useragent/rust/Cargo.lock @@ -0,0 +1,5086 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "accesskit" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a" +dependencies = [ + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842fd8203e6dfcf531d24f5bac792088edfba7d6b35844fead191603fb32a260" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "phf", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534bc3fdc89a64a1db3c46b33c198fde2b7c3c7d094e5809c8c8bf2970c18243" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e549dd7c6562b6a2ea807b44726e6241707db054a817dc4c7e2b8d3b39bfac" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allo-isolate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" +dependencies = [ + "anyhow", + "atomic", + "backtrace", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.11.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbiter-crypto" +version = "0.1.0" +dependencies = [ + "base64", + "memsafe", + "ml-dsa", + "rand", +] + +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.1", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.11.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.6", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.1", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "137c0ce4ce4152ff7e223a7ce22ee1057cdff61fce0a45c32459c3ccec64868d" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6e995b8e434d65aefd12c4519221be3e8f38efd77804ef39ca10553f4ad7063" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "pollster", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "wgpu", + "windows-sys 0.61.2", + "winit", +] + +[[package]] +name = "egui" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34aaf627da598dfadd64b0fee6101d22e9c451d1e5348157312720b7f459f0f" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.11.0", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "egui-wgpu" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71033ff78b041c9c363450f4498ff95468ef3ecbcc71a62f67036a6207d98fa4" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 2.0.18", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a2881b2bf1a305e413e644af63f836737a33d85077705ff808e88f902ff742" +dependencies = [ + "accesskit_winit", + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b28d39ab6c0cac238190e6cb1e8c9047d02cb470ab942a7a3302e4cb3a8e74" +dependencies = [ + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a05cd8bdf3b598488c627ca97c7fe8909448ffa26278dd3c7e535cdb554d721" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "epaint" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3017dd67f147a697ee0c8484fb568fd9553e2a0c114be5020dbbc11962841" +dependencies = [ + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "self_cell", + "skrifa", + "smallvec", + "vello_cpu", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3b85a2bb775a3ab02d077a65cc31575c11b2584581913253cc11ce49f48bba" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fearless_simd" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "flutter_rust_bridge" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0884853aae8a6517b5b58cf36f55da487f2fe110e1686938eb29b6640aae4a5" +dependencies = [ + "allo-isolate", + "android_logger", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce32f35f710ced8c5aa557f023f1a624e737b5460cee2b70fcd3a8df09e1b" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9237c6d82152100c691fb77ea18037b402bcc7257d2c876a4ffac81bc22a1c" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.11.0", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.11.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", + "zeroize", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memsafe" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f3e199d5e8adf073900f95b635f1192c394a442ed406c16dc7991b74501645" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ml-dsa" +version = "0.1.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b2bb0ad6fa2b40396775bd56f51345171490fef993f46f91a876ecdbdaea55" +dependencies = [ + "const-oid", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8", + "rand_core", + "sha3", + "signature", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164eb3faeaecbd14b0b2a917c1b4d0c035097a9c559b0bed85c2cdd032bc8faa" +dependencies = [ + "hybrid-array", + "num-traits", + "zeroize", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.3", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "orbclient" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "peniko" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2b6aadb221872732e87d465213e9be5af2849b0e8cc5300a8ba98fffa2e00a" +dependencies = [ + "bytemuck", + "color", + "kurbo", + "linebender_resource_handle", + "smallvec", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rust_lib_arbiter" +version = "0.1.0" +dependencies = [ + "anyhow", + "arbiter-crypto", + "eframe", + "egui", + "flutter_rust_bridge", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.2", + "keccak", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "3.0.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" +dependencies = [ + "digest 0.11.2", + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "num_cpus", + "pin-project-lite", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.2", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vello_common" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd1a4c633ce09e7d713df1a6e036644a125e15e0c169cfb5180ddf5836ca04b" +dependencies = [ + "bytemuck", + "fearless_simd", + "hashbrown 0.16.1", + "log", + "peniko", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_cpu" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162bfe48aabf6a9fdcd401b628c7d9f260c2cbabb343c70a65feba6f7849edc" +dependencies = [ + "bytemuck", + "hashbrown 0.16.1", + "vello_common", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" +dependencies = [ + "arrayvec", + "bitflags 2.11.0", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.11.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7b75e72f49035f000dd5262e4126242e92a090a4fd75931ecfe7e60784e6fa" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.11.0", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.11.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow 0.7.15", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac" +dependencies = [ + "quick-xml 0.38.4", + "serde", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.15", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow 0.7.15", +] diff --git a/useragent/rust/Cargo.toml b/useragent/rust/Cargo.toml new file mode 100644 index 0000000..4cbf5e2 --- /dev/null +++ b/useragent/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust_lib_arbiter" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +eframe = "0.34.1" +egui = "0.34.1" +flutter_rust_bridge = "=2.12.0" +arbiter-crypto = {path = "../../server/crates/arbiter-crypto"} +anyhow = "1.0.102" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } diff --git a/useragent/rust/src/api/mod.rs b/useragent/rust/src/api/mod.rs new file mode 100644 index 0000000..baaab3c --- /dev/null +++ b/useragent/rust/src/api/mod.rs @@ -0,0 +1,29 @@ +use anyhow::anyhow; +use flutter_rust_bridge::frb; +use arbiter_crypto::authn::{self, USERAGENT_CONTEXT}; + +#[frb(opaque)] +pub struct MldsaKey(authn::SigningKey); + +impl MldsaKey { + pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + let bytes: [u8; 32] = bytes.try_into().map_err(|_| anyhow!("Invalid key length"))?; + Ok(Self(authn::SigningKey::from_seed(bytes))) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_seed().to_vec() + } + + pub fn sign(&self, message: &[u8]) -> anyhow::Result> { + Ok(self.0.sign_message(message, USERAGENT_CONTEXT)?.to_bytes()) + } + + pub fn generate() -> Self { + Self(authn::SigningKey::generate()) + } + + pub fn get_public_key(&self) -> Vec { + self.0.public_key().to_bytes().to_vec() + } +} \ No newline at end of file diff --git a/useragent/rust/src/frb_generated.rs b/useragent/rust/src/frb_generated.rs new file mode 100644 index 0000000..7489044 --- /dev/null +++ b/useragent/rust/src/frb_generated.rs @@ -0,0 +1,558 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +#![allow( + non_camel_case_types, + unused, + non_snake_case, + clippy::needless_return, + clippy::redundant_closure_call, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::unused_unit, + clippy::double_parens, + clippy::let_and_return, + clippy::too_many_arguments, + clippy::match_single_binding, + clippy::clone_on_copy, + clippy::let_unit_value, + clippy::deref_addrof, + clippy::explicit_auto_deref, + clippy::borrow_deref_ref, + clippy::uninlined_format_args, + clippy::needless_borrow +)] + +// Section: imports + +use crate::api::*; +use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; +use flutter_rust_bridge::{Handler, IntoIntoDart}; + +// Section: boilerplate + +flutter_rust_bridge::frb_generated_boilerplate!( + default_stream_sink_codec = SseCodec, + default_rust_opaque = RustOpaqueMoi, + default_rust_auto_opaque = RustAutoOpaqueMoi, +); +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -437661335; + +// Section: executor + +flutter_rust_bridge::frb_generated_default_handler!(); + +// Section: wire_funcs + +fn wire__crate__api__MldsaKey_from_bytes_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MldsaKey_from_bytes", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_bytes = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = crate::api::MldsaKey::from_bytes(&api_bytes)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__MldsaKey_generate_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MldsaKey_generate", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::MldsaKey::generate())?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__MldsaKey_get_public_key_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MldsaKey_get_public_key", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok(crate::api::MldsaKey::get_public_key( + &*api_that_guard, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__MldsaKey_sign_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MldsaKey_sign", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_message = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::MldsaKey::sign(&*api_that_guard, &api_message)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__MldsaKey_to_bytes_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MldsaKey_to_bytes", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + Result::<_, ()>::Ok(crate::api::MldsaKey::to_bytes(&*api_that_guard))?; + Ok(output_ok) + })()) + } + }, + ) +} + +// Section: related_funcs + +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); + +// Section: dart2rust + +impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::anyhow::anyhow!("{}", inner); + } +} + +impl SseDecode for MldsaKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return String::from_utf8(inner).unwrap(); + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() + } +} + +impl SseDecode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} +} + +impl SseDecode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() as _ + } +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +fn pde_ffi_dispatcher_primary_impl( + func_id: i32, + port: flutter_rust_bridge::for_generated::MessagePort, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 1 => wire__crate__api__MldsaKey_from_bytes_impl(port, ptr, rust_vec_len, data_len), + 2 => wire__crate__api__MldsaKey_generate_impl(port, ptr, rust_vec_len, data_len), + 3 => wire__crate__api__MldsaKey_get_public_key_impl(port, ptr, rust_vec_len, data_len), + 4 => wire__crate__api__MldsaKey_sign_impl(port, ptr, rust_vec_len, data_len), + 5 => wire__crate__api__MldsaKey_to_bytes_impl(port, ptr, rust_vec_len, data_len), + _ => unreachable!(), + } +} + +fn pde_ffi_dispatcher_sync_impl( + func_id: i32, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + _ => unreachable!(), + } +} + +// Section: rust2dart + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} + +impl flutter_rust_bridge::IntoIntoDart> for MldsaKey { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(format!("{:?}", self), serializer); + } +} + +impl SseEncode for MldsaKey { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_bytes(), serializer); + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self).unwrap(); + } +} + +impl SseEncode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + +impl SseEncode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer + .cursor + .write_u64::(self as _) + .unwrap(); + } +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +#[cfg(not(target_family = "wasm"))] +mod io { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use crate::api::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_io!(); + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_arbiter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_arbiter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +mod web { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use crate::api::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::wasm_bindgen; + use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_web!(); + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(target_family = "wasm")] +pub use web::*; diff --git a/useragent/rust/src/lib.rs b/useragent/rust/src/lib.rs new file mode 100644 index 0000000..cbb071f --- /dev/null +++ b/useragent/rust/src/lib.rs @@ -0,0 +1,2 @@ +pub mod api; +mod frb_generated; diff --git a/useragent/rust_builder/.gitignore b/useragent/rust_builder/.gitignore new file mode 100644 index 0000000..ac5aa98 --- /dev/null +++ b/useragent/rust_builder/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/useragent/rust_builder/README.md b/useragent/rust_builder/README.md new file mode 100644 index 0000000..922615f --- /dev/null +++ b/useragent/rust_builder/README.md @@ -0,0 +1 @@ +Please ignore this folder, which is just glue to build Rust with Flutter. \ No newline at end of file diff --git a/useragent/rust_builder/android/.gitignore b/useragent/rust_builder/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/useragent/rust_builder/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/useragent/rust_builder/android/build.gradle b/useragent/rust_builder/android/build.gradle new file mode 100644 index 0000000..222ff73 --- /dev/null +++ b/useragent/rust_builder/android/build.gradle @@ -0,0 +1,56 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.flutter_rust_bridge.rust_lib_arbiter' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.flutter_rust_bridge.rust_lib_arbiter' + } + + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 33 + + // Use the NDK version + // declared in /android/app/build.gradle file of the Flutter project. + // Replace it with a version number if this plugin requires a specfic NDK version. + // (e.g. ndkVersion "23.1.7779620") + ndkVersion android.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + } +} + +apply from: "../cargokit/gradle/plugin.gradle" +cargokit { + manifestDir = "../../rust" + libname = "rust_lib_arbiter" +} diff --git a/useragent/rust_builder/android/settings.gradle b/useragent/rust_builder/android/settings.gradle new file mode 100644 index 0000000..cb25a60 --- /dev/null +++ b/useragent/rust_builder/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'rust_lib_arbiter' diff --git a/useragent/rust_builder/android/src/main/AndroidManifest.xml b/useragent/rust_builder/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d22bc1b --- /dev/null +++ b/useragent/rust_builder/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/useragent/rust_builder/cargokit/.gitignore b/useragent/rust_builder/cargokit/.gitignore new file mode 100644 index 0000000..cf7bb86 --- /dev/null +++ b/useragent/rust_builder/cargokit/.gitignore @@ -0,0 +1,4 @@ +target +.dart_tool +*.iml +!pubspec.lock diff --git a/useragent/rust_builder/cargokit/LICENSE b/useragent/rust_builder/cargokit/LICENSE new file mode 100644 index 0000000..d33a5fe --- /dev/null +++ b/useragent/rust_builder/cargokit/LICENSE @@ -0,0 +1,42 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Copyright 2022 Matej Knopp + +================================================================================ + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ + +APACHE LICENSE, VERSION 2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/useragent/rust_builder/cargokit/README b/useragent/rust_builder/cargokit/README new file mode 100644 index 0000000..398474d --- /dev/null +++ b/useragent/rust_builder/cargokit/README @@ -0,0 +1,11 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Experimental repository to provide glue for seamlessly integrating cargo build +with flutter plugins and packages. + +See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ +for a tutorial on how to use Cargokit. + +Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. + diff --git a/useragent/rust_builder/cargokit/build_pod.sh b/useragent/rust_builder/cargokit/build_pod.sh new file mode 100755 index 0000000..ed0e0d9 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_pod.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -e + +BASEDIR=$(dirname "$0") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +BASEDIR=$(cd "$BASEDIR" ; pwd -P) + +# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project +NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` + +export PATH=${NEW_PATH%?} # remove trailing : + +env + +# Platform name (macosx, iphoneos, iphonesimulator) +export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME + +# Arctive architectures (arm64, armv7, x86_64), space separated. +export CARGOKIT_DARWIN_ARCHS=$ARCHS + +# Current build configuration (Debug, Release) +export CARGOKIT_CONFIGURATION=$CONFIGURATION + +# Path to directory containing Cargo.toml. +export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 + +# Temporary directory for build artifacts. +export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR + +# Output directory for final artifacts. +export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME + +# Directory to store built tool artifacts. +export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool + +# Directory inside root project. Not necessarily the top level directory of root project. +export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT + +FLUTTER_EXPORT_BUILD_ENVIRONMENT=( + "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS + "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS +) + +for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" +do + if [[ -f "$path" ]]; then + source "$path" + fi +done + +sh "$BASEDIR/run_build_tool.sh" build-pod "$@" + +# Make a symlink from built framework to phony file, which will be used as input to +# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate +# attribute on custom build phase) +ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" +ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" diff --git a/useragent/rust_builder/cargokit/build_tool/README.md b/useragent/rust_builder/cargokit/build_tool/README.md new file mode 100644 index 0000000..a878c27 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/README.md @@ -0,0 +1,5 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`, and example unit test in `test/`. diff --git a/useragent/rust_builder/cargokit/build_tool/analysis_options.yaml b/useragent/rust_builder/cargokit/build_tool/analysis_options.yaml new file mode 100644 index 0000000..0e16a8b --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/analysis_options.yaml @@ -0,0 +1,34 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +linter: + rules: + - prefer_relative_imports + - directives_ordering + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/useragent/rust_builder/cargokit/build_tool/bin/build_tool.dart b/useragent/rust_builder/cargokit/build_tool/bin/build_tool.dart new file mode 100644 index 0000000..268eb52 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/bin/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:build_tool/build_tool.dart' as build_tool; + +void main(List arguments) { + build_tool.runMain(arguments); +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/build_tool.dart b/useragent/rust_builder/cargokit/build_tool/lib/build_tool.dart new file mode 100644 index 0000000..7c1bb75 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'src/build_tool.dart' as build_tool; + +Future runMain(List args) async { + return build_tool.runMain(args); +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/android_environment.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/android_environment.dart new file mode 100644 index 0000000..15fc9ee --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/android_environment.dart @@ -0,0 +1,195 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; +import 'package:version/version.dart'; + +import 'target.dart'; +import 'util.dart'; + +class AndroidEnvironment { + AndroidEnvironment({ + required this.sdkPath, + required this.ndkVersion, + required this.minSdkVersion, + required this.targetTempDir, + required this.target, + }); + + static void clangLinkerWrapper(List args) { + final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; + if (clang == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); + } + final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; + if (target == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); + } + + runCommand(clang, [ + target, + ...args, + ]); + } + + /// Full path to Android SDK. + final String sdkPath; + + /// Full version of Android NDK. + final String ndkVersion; + + /// Minimum supported SDK version. + final int minSdkVersion; + + /// Target directory for build artifacts. + final String targetTempDir; + + /// Target being built. + final Target target; + + bool ndkIsInstalled() { + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); + return ndkPackageXml.existsSync(); + } + + void installNdk({ + required String javaHome, + }) { + final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; + final sdkManager = path.join( + sdkPath, + 'cmdline-tools', + 'latest', + 'bin', + 'sdkmanager$sdkManagerExtension', + ); + + log.info('Installing NDK $ndkVersion'); + runCommand(sdkManager, [ + '--install', + 'ndk;$ndkVersion', + ], environment: { + 'JAVA_HOME': javaHome, + }); + } + + Future> buildEnvironment() async { + final hostArch = Platform.isMacOS + ? "darwin-x86_64" + : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); + + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final toolchainPath = path.join( + ndkPath, + 'toolchains', + 'llvm', + 'prebuilt', + hostArch, + 'bin', + ); + + final minSdkVersion = + math.max(target.androidMinSdkVersion!, this.minSdkVersion); + + final exe = Platform.isWindows ? '.exe' : ''; + + final arKey = 'AR_${target.rust}'; + final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] + .map((e) => path.join(toolchainPath, e)) + .firstWhereOrNull((element) => File(element).existsSync()); + if (arValue == null) { + throw Exception('Failed to find ar for $target in $toolchainPath'); + } + + final targetArg = '--target=${target.rust}$minSdkVersion'; + + final ccKey = 'CC_${target.rust}'; + final ccValue = path.join(toolchainPath, 'clang$exe'); + final cfFlagsKey = 'CFLAGS_${target.rust}'; + final cFlagsValue = targetArg; + + final cxxKey = 'CXX_${target.rust}'; + final cxxValue = path.join(toolchainPath, 'clang++$exe'); + final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; + final cxxFlagsValue = targetArg; + + final linkerKey = + 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); + + final ranlibKey = 'RANLIB_${target.rust}'; + final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); + + final ndkVersionParsed = Version.parse(ndkVersion); + final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; + final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); + + final runRustTool = + Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; + + final packagePath = (await Isolate.resolvePackageUri( + Uri.parse('package:build_tool/buildtool.dart')))! + .toFilePath(); + final selfPath = path.canonicalize(path.join( + packagePath, + '..', + '..', + '..', + runRustTool, + )); + + // Make sure that run_build_tool is working properly even initially launched directly + // through dart run. + final toolTempDir = + Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; + + return { + arKey: arValue, + ccKey: ccValue, + cfFlagsKey: cFlagsValue, + cxxKey: cxxValue, + cxxFlagsKey: cxxFlagsValue, + ranlibKey: ranlibValue, + rustFlagsKey: rustFlagsValue, + linkerKey: selfPath, + // Recognized by main() so we know when we're acting as a wrapper + '_CARGOKIT_NDK_LINK_TARGET': targetArg, + '_CARGOKIT_NDK_LINK_CLANG': ccValue, + 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, + }; + } + + // Workaround for libgcc missing in NDK23, inspired by cargo-ndk + String _libGccWorkaround(String buildDir, Version ndkVersion) { + final workaroundDir = path.join( + buildDir, + 'cargokit', + 'libgcc_workaround', + '${ndkVersion.major}', + ); + Directory(workaroundDir).createSync(recursive: true); + if (ndkVersion.major >= 23) { + File(path.join(workaroundDir, 'libgcc.a')) + .writeAsStringSync('INPUT(-lunwind)'); + } else { + // Other way around, untested, forward libgcc.a from libunwind once Rust + // gets updated for NDK23+. + File(path.join(workaroundDir, 'libunwind.a')) + .writeAsStringSync('INPUT(-lgcc)'); + } + + var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; + if (rustFlags.isNotEmpty) { + rustFlags = '$rustFlags\x1f'; + } + rustFlags = '$rustFlags-L\x1f$workaroundDir'; + return rustFlags; + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart new file mode 100644 index 0000000..e608cec --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/artifacts_provider.dart @@ -0,0 +1,266 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'builder.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'rustup.dart'; +import 'target.dart'; + +class Artifact { + /// File system location of the artifact. + final String path; + + /// Actual file name that the artifact should have in destination folder. + final String finalFileName; + + AritifactType get type { + if (finalFileName.endsWith('.dll') || + finalFileName.endsWith('.dll.lib') || + finalFileName.endsWith('.pdb') || + finalFileName.endsWith('.so') || + finalFileName.endsWith('.dylib')) { + return AritifactType.dylib; + } else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) { + return AritifactType.staticlib; + } else { + throw Exception('Unknown artifact type for $finalFileName'); + } + } + + Artifact({ + required this.path, + required this.finalFileName, + }); +} + +final _log = Logger('artifacts_provider'); + +class ArtifactProvider { + ArtifactProvider({ + required this.environment, + required this.userOptions, + }); + + final BuildEnvironment environment; + final CargokitUserOptions userOptions; + + Future>> getArtifacts(List targets) async { + final result = await _getPrecompiledArtifacts(targets); + + final pendingTargets = List.of(targets); + pendingTargets.removeWhere((element) => result.containsKey(element)); + + if (pendingTargets.isEmpty) { + return result; + } + + final rustup = Rustup(); + for (final target in targets) { + final builder = RustBuilder(target: target, environment: environment); + builder.prepare(rustup); + _log.info('Building ${environment.crateInfo.packageName} for $target'); + final targetDir = await builder.build(); + // For local build accept both static and dynamic libraries. + final artifactNames = { + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.dylib, + remote: false, + ), + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.staticlib, + remote: false, + ) + }; + final artifacts = artifactNames + .map((artifactName) => Artifact( + path: path.join(targetDir, artifactName), + finalFileName: artifactName, + )) + .where((element) => File(element.path).existsSync()) + .toList(); + result[target] = artifacts; + } + return result; + } + + Future>> _getPrecompiledArtifacts( + List targets) async { + if (userOptions.usePrecompiledBinaries == false) { + _log.info('Precompiled binaries are disabled'); + return {}; + } + if (environment.crateOptions.precompiledBinaries == null) { + _log.fine('Precompiled binaries not enabled for this crate'); + return {}; + } + + final start = Stopwatch()..start(); + final crateHash = CrateHash.compute(environment.manifestDir, + tempStorage: environment.targetTempDir); + _log.fine( + 'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms'); + + final downloadedArtifactsDir = + path.join(environment.targetTempDir, 'precompiled', crateHash); + Directory(downloadedArtifactsDir).createSync(recursive: true); + + final res = >{}; + + for (final target in targets) { + final requiredArtifacts = getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + remote: true, + ); + final artifactsForTarget = []; + + for (final artifact in requiredArtifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final downloadedPath = path.join(downloadedArtifactsDir, fileName); + if (!File(downloadedPath).existsSync()) { + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + await _tryDownloadArtifacts( + crateHash: crateHash, + fileName: fileName, + signatureFileName: signatureFileName, + finalPath: downloadedPath, + ); + } + if (File(downloadedPath).existsSync()) { + artifactsForTarget.add(Artifact( + path: downloadedPath, + finalFileName: artifact, + )); + } else { + break; + } + } + + // Only provide complete set of artifacts. + if (artifactsForTarget.length == requiredArtifacts.length) { + _log.fine('Found precompiled artifacts for $target'); + res[target] = artifactsForTarget; + } + } + + return res; + } + + static Future _get(Uri url, {Map? headers}) async { + int attempt = 0; + const maxAttempts = 10; + while (true) { + try { + return await get(url, headers: headers); + } on SocketException catch (e) { + // Try to detect reset by peer error and retry. + if (attempt++ < maxAttempts && + (e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) { + _log.severe( + 'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...'); + await Future.delayed(Duration(seconds: 1)); + continue; + } else { + rethrow; + } + } + } + } + + Future _tryDownloadArtifacts({ + required String crateHash, + required String fileName, + required String signatureFileName, + required String finalPath, + }) async { + final precompiledBinaries = environment.crateOptions.precompiledBinaries!; + final prefix = precompiledBinaries.uriPrefix; + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName'); + _log.fine('Downloading signature from $signatureUrl'); + final signature = await _get(signatureUrl); + if (signature.statusCode == 404) { + _log.warning( + 'Precompiled binaries not available for crate hash $crateHash ($fileName)'); + return; + } + if (signature.statusCode != 200) { + _log.severe( + 'Failed to download signature $signatureUrl: status ${signature.statusCode}'); + return; + } + _log.fine('Downloading binary from $url'); + final res = await _get(url); + if (res.statusCode != 200) { + _log.severe('Failed to download binary $url: status ${res.statusCode}'); + return; + } + if (verify( + precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) { + File(finalPath).writeAsBytesSync(res.bodyBytes); + } else { + _log.shout('Signature verification failed! Ignoring binary.'); + } + } +} + +enum AritifactType { + staticlib, + dylib, +} + +AritifactType artifactTypeForTarget(Target target) { + if (target.darwinPlatform != null) { + return AritifactType.staticlib; + } else { + return AritifactType.dylib; + } +} + +List getArtifactNames({ + required Target target, + required String libraryName, + required bool remote, + AritifactType? aritifactType, +}) { + aritifactType ??= artifactTypeForTarget(target); + if (target.darwinArch != null) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.dylib']; + } + } else if (target.rust.contains('-windows-')) { + if (aritifactType == AritifactType.staticlib) { + return ['$libraryName.lib']; + } else { + return [ + '$libraryName.dll', + '$libraryName.dll.lib', + if (!remote) '$libraryName.pdb' + ]; + } + } else if (target.rust.contains('-linux-')) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.so']; + } + } else { + throw Exception("Unsupported target: ${target.rust}"); + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart new file mode 100644 index 0000000..6f3b2a4 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/build_cmake.dart @@ -0,0 +1,40 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +class BuildCMake { + final CargokitUserOptions userOptions; + + BuildCMake({required this.userOptions}); + + Future build() async { + final targetPlatform = Environment.targetPlatform; + final target = Target.forFlutterName(Environment.targetPlatform); + if (target == null) { + throw Exception("Unknown target platform: $targetPlatform"); + } + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts([target]); + + final libs = artifacts[target]!; + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path) + .copySync(path.join(Environment.outputDir, lib.finalFileName)); + } + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart new file mode 100644 index 0000000..7e61fcb --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/build_gradle.dart @@ -0,0 +1,49 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +final log = Logger('build_gradle'); + +class BuildGradle { + BuildGradle({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.targetPlatforms.map((arch) { + final target = Target.forFlutterName(arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: true); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + for (final target in targets) { + final libs = artifacts[target]!; + final outputDir = path.join(Environment.outputDir, target.android!); + Directory(outputDir).createSync(recursive: true); + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); + } + } + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/build_pod.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/build_pod.dart new file mode 100644 index 0000000..8a9c0db --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/build_pod.dart @@ -0,0 +1,89 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; +import 'util.dart'; + +class BuildPod { + BuildPod({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.darwinArchs.map((arch) { + final target = Target.forDarwin( + platformName: Environment.darwinPlatformName, darwinAarch: arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + void performLipo(String targetFile, Iterable sourceFiles) { + runCommand("lipo", [ + '-create', + ...sourceFiles, + '-output', + targetFile, + ]); + } + + final outputDir = Environment.outputDir; + + Directory(outputDir).createSync(recursive: true); + + final staticLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.staticlib) + .toList(); + final dynamicLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.dylib) + .toList(); + + final libName = environment.crateInfo.packageName; + + // If there is static lib, use it and link it with pod + if (staticLibs.isNotEmpty) { + final finalTargetFile = path.join(outputDir, "lib$libName.a"); + performLipo(finalTargetFile, staticLibs.map((e) => e.path)); + } else { + // Otherwise try to replace bundle dylib with our dylib + final bundlePaths = [ + '$libName.framework/Versions/A/$libName', + '$libName.framework/$libName', + ]; + + for (final bundlePath in bundlePaths) { + final targetFile = path.join(outputDir, bundlePath); + if (File(targetFile).existsSync()) { + performLipo(targetFile, dynamicLibs.map((e) => e.path)); + + // Replace absolute id with @rpath one so that it works properly + // when moved to Frameworks. + runCommand("install_name_tool", [ + '-id', + '@rpath/$bundlePath', + targetFile, + ]); + return; + } + } + throw Exception('Unable to find bundle for dynamic library'); + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/build_tool.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/build_tool.dart new file mode 100644 index 0000000..70dfe0e --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/build_tool.dart @@ -0,0 +1,276 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; + +import 'android_environment.dart'; +import 'build_cmake.dart'; +import 'build_gradle.dart'; +import 'build_pod.dart'; +import 'logging.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; +import 'util.dart'; +import 'verify_binaries.dart'; + +final log = Logger('build_tool'); + +abstract class BuildCommand extends Command { + Future runBuildCommand(CargokitUserOptions options); + + @override + Future run() async { + final options = CargokitUserOptions.load(); + + if (options.verboseLogging || + Platform.environment['CARGOKIT_VERBOSE'] == '1') { + enableVerboseLogging(); + } + + await runBuildCommand(options); + } +} + +class BuildPodCommand extends BuildCommand { + @override + final name = 'build-pod'; + + @override + final description = 'Build cocoa pod library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildPod(userOptions: options); + await build.build(); + } +} + +class BuildGradleCommand extends BuildCommand { + @override + final name = 'build-gradle'; + + @override + final description = 'Build android library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildGradle(userOptions: options); + await build.build(); + } +} + +class BuildCMakeCommand extends BuildCommand { + @override + final name = 'build-cmake'; + + @override + final description = 'Build CMake library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildCMake(userOptions: options); + await build.build(); + } +} + +class GenKeyCommand extends Command { + @override + final name = 'gen-key'; + + @override + final description = 'Generate key pair for signing precompiled binaries'; + + @override + void run() { + final kp = generateKey(); + final private = HEX.encode(kp.privateKey.bytes); + final public = HEX.encode(kp.publicKey.bytes); + print("Private Key: $private"); + print("Public Key: $public"); + } +} + +class PrecompileBinariesCommand extends Command { + PrecompileBinariesCommand() { + argParser + ..addOption( + 'repository', + mandatory: true, + help: 'Github repository slug in format owner/name', + ) + ..addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ) + ..addMultiOption('target', + help: 'Rust target triple of artifact to build.\n' + 'Can be specified multiple times or omitted in which case\n' + 'all targets for current platform will be built.') + ..addOption( + 'android-sdk-location', + help: 'Location of Android SDK (if available)', + ) + ..addOption( + 'android-ndk-version', + help: 'Android NDK version (if available)', + ) + ..addOption( + 'android-min-sdk-version', + help: 'Android minimum rquired version (if available)', + ) + ..addOption( + 'temp-dir', + help: 'Directory to store temporary build artifacts', + ) + ..addOption( + 'glibc-version', + help: 'GLIBC version to use for linux builds', + ) + ..addFlag( + "verbose", + abbr: "v", + defaultsTo: false, + help: "Enable verbose logging", + ); + } + + @override + final name = 'precompile-binaries'; + + @override + final description = 'Prebuild and upload binaries\n' + 'Private key must be passed through PRIVATE_KEY environment variable. ' + 'Use gen_key through generate priave key.\n' + 'Github token must be passed as GITHUB_TOKEN environment variable.\n'; + + @override + Future run() async { + final verbose = argResults!['verbose'] as bool; + if (verbose) { + enableVerboseLogging(); + } + + final privateKeyString = Platform.environment['PRIVATE_KEY']; + if (privateKeyString == null) { + throw ArgumentError('Missing PRIVATE_KEY environment variable'); + } + final githubToken = Platform.environment['GITHUB_TOKEN']; + if (githubToken == null) { + throw ArgumentError('Missing GITHUB_TOKEN environment variable'); + } + final privateKey = HEX.decode(privateKeyString); + if (privateKey.length != 64) { + throw ArgumentError('Private key must be 64 bytes long'); + } + final manifestDir = argResults!['manifest-dir'] as String; + if (!Directory(manifestDir).existsSync()) { + throw ArgumentError('Manifest directory does not exist: $manifestDir'); + } + String? androidMinSdkVersionString = + argResults!['android-min-sdk-version'] as String?; + int? androidMinSdkVersion; + if (androidMinSdkVersionString != null) { + androidMinSdkVersion = int.tryParse(androidMinSdkVersionString); + if (androidMinSdkVersion == null) { + throw ArgumentError( + 'Invalid android-min-sdk-version: $androidMinSdkVersionString'); + } + } + final targetStrigns = argResults!['target'] as List; + final targets = targetStrigns.map((target) { + final res = Target.forRustTriple(target); + if (res == null) { + throw ArgumentError('Invalid target: $target'); + } + return res; + }).toList(growable: false); + final precompileBinaries = PrecompileBinaries( + privateKey: PrivateKey(privateKey), + githubToken: githubToken, + manifestDir: manifestDir, + repositorySlug: RepositorySlug.full(argResults!['repository'] as String), + targets: targets, + androidSdkLocation: argResults!['android-sdk-location'] as String?, + androidNdkVersion: argResults!['android-ndk-version'] as String?, + androidMinSdkVersion: androidMinSdkVersion, + tempDir: argResults!['temp-dir'] as String?, + glibcVersion: argResults!['glibc-version'] as String?, + ); + + await precompileBinaries.run(); + } +} + +class VerifyBinariesCommand extends Command { + VerifyBinariesCommand() { + argParser.addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ); + } + + @override + final name = "verify-binaries"; + + @override + final description = 'Verifies published binaries\n' + 'Checks whether there is a binary published for each targets\n' + 'and checks the signature.'; + + @override + Future run() async { + final manifestDir = argResults!['manifest-dir'] as String; + final verifyBinaries = VerifyBinaries( + manifestDir: manifestDir, + ); + await verifyBinaries.run(); + } +} + +Future runMain(List args) async { + try { + // Init logging before options are loaded + initLogging(); + + if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) { + return AndroidEnvironment.clangLinkerWrapper(args); + } + + final runner = CommandRunner('build_tool', 'Cargokit built_tool') + ..addCommand(BuildPodCommand()) + ..addCommand(BuildGradleCommand()) + ..addCommand(BuildCMakeCommand()) + ..addCommand(GenKeyCommand()) + ..addCommand(PrecompileBinariesCommand()) + ..addCommand(VerifyBinariesCommand()); + + await runner.run(args); + } on ArgumentError catch (e) { + stderr.writeln(e.toString()); + exit(1); + } catch (e, s) { + log.severe(kDoubleSeparator); + log.severe('Cargokit BuildTool failed with error:'); + log.severe(kSeparator); + log.severe(e); + // This tells user to install Rust, there's no need to pollute the log with + // stack trace. + if (e is! RustupNotFoundException) { + log.severe(kSeparator); + log.severe(s); + log.severe(kSeparator); + log.severe('BuildTool arguments: $args'); + } + log.severe(kDoubleSeparator); + exit(1); + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/builder.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/builder.dart new file mode 100644 index 0000000..cd5269f --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/builder.dart @@ -0,0 +1,209 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'android_environment.dart'; +import 'cargo.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; +import 'util.dart'; + +final _log = Logger('builder'); + +enum BuildConfiguration { + debug, + release, + profile, +} + +extension on BuildConfiguration { + bool get isDebug => this == BuildConfiguration.debug; + String get rustName => switch (this) { + BuildConfiguration.debug => 'debug', + BuildConfiguration.release => 'release', + BuildConfiguration.profile => 'release', + }; +} + +class BuildException implements Exception { + final String message; + + BuildException(this.message); + + @override + String toString() { + return 'BuildException: $message'; + } +} + +class BuildEnvironment { + final BuildConfiguration configuration; + final CargokitCrateOptions crateOptions; + final String targetTempDir; + final String manifestDir; + final CrateInfo crateInfo; + + final bool isAndroid; + final String? androidSdkPath; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? javaHome; + + final String? glibcVersion; + + BuildEnvironment({ + required this.configuration, + required this.crateOptions, + required this.targetTempDir, + required this.manifestDir, + required this.crateInfo, + required this.isAndroid, + this.androidSdkPath, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.javaHome, + this.glibcVersion, + }); + + static BuildConfiguration parseBuildConfiguration(String value) { + // XCode configuration adds the flavor to configuration name. + final firstSegment = value.split('-').first; + final buildConfiguration = BuildConfiguration.values.firstWhereOrNull( + (e) => e.name == firstSegment, + ); + if (buildConfiguration == null) { + _log.warning('Unknown build configuraiton $value, will assume release'); + return BuildConfiguration.release; + } + return buildConfiguration; + } + + static BuildEnvironment fromEnvironment({ + required bool isAndroid, + }) { + final buildConfiguration = + parseBuildConfiguration(Environment.configuration); + final manifestDir = Environment.manifestDir; + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + final crateInfo = CrateInfo.load(manifestDir); + return BuildEnvironment( + configuration: buildConfiguration, + crateOptions: crateOptions, + targetTempDir: Environment.targetTempDir, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: isAndroid, + androidSdkPath: isAndroid ? Environment.sdkPath : null, + androidNdkVersion: isAndroid ? Environment.ndkVersion : null, + androidMinSdkVersion: + isAndroid ? int.parse(Environment.minSdkVersion) : null, + javaHome: isAndroid ? Environment.javaHome : null, + ); + } +} + +class RustBuilder { + final Target target; + final BuildEnvironment environment; + + RustBuilder({ + required this.target, + required this.environment, + }); + + void prepare( + Rustup rustup, + ) { + final toolchain = _toolchain; + if (rustup.installedTargets(toolchain) == null) { + rustup.installToolchain(toolchain); + } + if (toolchain == 'nightly') { + rustup.installRustSrcForNightly(); + } + if (!rustup.installedTargets(toolchain)!.contains(target.rust)) { + rustup.installTarget(target.rust, toolchain: toolchain); + } + if (environment.glibcVersion != null) { + rustup.installZigBuild(toolchain); + } + } + + CargoBuildOptions? get _buildOptions => + environment.crateOptions.cargo[environment.configuration]; + + String get _toolchain => _buildOptions?.toolchain.name ?? 'stable'; + + /// Returns the path of directory containing build artifacts. + Future build() async { + final extraArgs = _buildOptions?.flags ?? []; + final manifestPath = path.join(environment.manifestDir, 'Cargo.toml'); + runCommand( + 'rustup', + [ + 'run', + _toolchain, + 'cargo', + (target.android == null && environment.glibcVersion != null) + ? 'zigbuild' + : 'build', + ...extraArgs, + '--manifest-path', + manifestPath, + '-p', + environment.crateInfo.packageName, + if (!environment.configuration.isDebug) '--release', + '--target', + target.rust + + ((target.android == null && environment.glibcVersion != null) + ? '.${environment.glibcVersion!}' + : ""), + '--target-dir', + environment.targetTempDir, + ], + environment: await _buildEnvironment(), + ); + return path.join( + environment.targetTempDir, + target.rust, + environment.configuration.rustName, + ); + } + + Future> _buildEnvironment() async { + if (target.android == null) { + return {}; + } else { + final sdkPath = environment.androidSdkPath; + final ndkVersion = environment.androidNdkVersion; + final minSdkVersion = environment.androidMinSdkVersion; + if (sdkPath == null) { + throw BuildException('androidSdkPath is not set'); + } + if (ndkVersion == null) { + throw BuildException('androidNdkVersion is not set'); + } + if (minSdkVersion == null) { + throw BuildException('androidMinSdkVersion is not set'); + } + final env = AndroidEnvironment( + sdkPath: sdkPath, + ndkVersion: ndkVersion, + minSdkVersion: minSdkVersion, + targetTempDir: environment.targetTempDir, + target: target, + ); + if (!env.ndkIsInstalled() && environment.javaHome != null) { + env.installNdk(javaHome: environment.javaHome!); + } + return env.buildEnvironment(); + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/cargo.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/cargo.dart new file mode 100644 index 0000000..0d8958f --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/cargo.dart @@ -0,0 +1,48 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:toml/toml.dart'; + +class ManifestException { + ManifestException(this.message, {required this.fileName}); + + final String? fileName; + final String message; + + @override + String toString() { + if (fileName != null) { + return 'Failed to parse package manifest at $fileName: $message'; + } else { + return 'Failed to parse package manifest: $message'; + } + } +} + +class CrateInfo { + CrateInfo({required this.packageName}); + + final String packageName; + + static CrateInfo parseManifest(String manifest, {final String? fileName}) { + final toml = TomlDocument.parse(manifest); + final package = toml.toMap()['package']; + if (package == null) { + throw ManifestException('Missing package section', fileName: fileName); + } + final name = package['name']; + if (name == null) { + throw ManifestException('Missing package name', fileName: fileName); + } + return CrateInfo(packageName: name); + } + + static CrateInfo load(String manifestDir) { + final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); + final manifest = manifestFile.readAsStringSync(); + return parseManifest(manifest, fileName: manifestFile.path); + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart new file mode 100644 index 0000000..0c4d88d --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/crate_hash.dart @@ -0,0 +1,124 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as path; + +class CrateHash { + /// Computes a hash uniquely identifying crate content. This takes into account + /// content all all .rs files inside the src directory, as well as Cargo.toml, + /// Cargo.lock, build.rs and cargokit.yaml. + /// + /// If [tempStorage] is provided, computed hash is stored in a file in that directory + /// and reused on subsequent calls if the crate content hasn't changed. + static String compute(String manifestDir, {String? tempStorage}) { + return CrateHash._( + manifestDir: manifestDir, + tempStorage: tempStorage, + )._compute(); + } + + CrateHash._({ + required this.manifestDir, + required this.tempStorage, + }); + + String _compute() { + final files = getFiles(); + final tempStorage = this.tempStorage; + if (tempStorage != null) { + final quickHash = _computeQuickHash(files); + final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); + quickHashFolder.createSync(recursive: true); + final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); + if (quickHashFile.existsSync()) { + return quickHashFile.readAsStringSync(); + } + final hash = _computeHash(files); + quickHashFile.writeAsStringSync(hash); + return hash; + } else { + return _computeHash(files); + } + } + + /// Computes a quick hash based on files stat (without reading contents). This + /// is used to cache the real hash, which is slower to compute since it involves + /// reading every single file. + String _computeQuickHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + final data = ByteData(8); + for (final file in files) { + input.add(utf8.encode(file.path)); + final stat = file.statSync(); + data.setUint64(0, stat.size); + input.add(data.buffer.asUint8List()); + data.setUint64(0, stat.modified.millisecondsSinceEpoch); + input.add(data.buffer.asUint8List()); + } + + input.close(); + return base64Url.encode(output.events.single.bytes); + } + + String _computeHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + void addTextFile(File file) { + // text Files are hashed by lines in case we're dealing with github checkout + // that auto-converts line endings. + final splitter = LineSplitter(); + if (file.existsSync()) { + final data = file.readAsStringSync(); + final lines = splitter.convert(data); + for (final line in lines) { + input.add(utf8.encode(line)); + } + } + } + + for (final file in files) { + addTextFile(file); + } + + input.close(); + final res = output.events.single; + + // Truncate to 128bits. + final hash = res.bytes.sublist(0, 16); + return hex.encode(hash); + } + + List getFiles() { + final src = Directory(path.join(manifestDir, 'src')); + final files = src + .listSync(recursive: true, followLinks: false) + .whereType() + .toList(); + files.sortBy((element) => element.path); + void addFile(String relative) { + final file = File(path.join(manifestDir, relative)); + if (file.existsSync()) { + files.add(file); + } + } + + addFile('Cargo.toml'); + addFile('Cargo.lock'); + addFile('build.rs'); + addFile('cargokit.yaml'); + return files; + } + + final String manifestDir; + final String? tempStorage; +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/environment.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/environment.dart new file mode 100644 index 0000000..996483a --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/environment.dart @@ -0,0 +1,68 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +extension on String { + String resolveSymlink() => File(this).resolveSymbolicLinksSync(); +} + +class Environment { + /// Current build configuration (debug or release). + static String get configuration => + _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); + + static bool get isDebug => configuration == 'debug'; + static bool get isRelease => configuration == 'release'; + + /// Temporary directory where Rust build artifacts are placed. + static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); + + /// Final output directory where the build artifacts are placed. + static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); + + /// Path to the crate manifest (containing Cargo.toml). + static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); + + /// Directory inside root project. Not necessarily root folder. Symlinks are + /// not resolved on purpose. + static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); + + // Pod + + /// Platform name (macosx, iphoneos, iphonesimulator). + static String get darwinPlatformName => + _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); + + /// List of architectures to build for (arm64, armv7, x86_64). + static List get darwinArchs => + _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); + + // Gradle + static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); + static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); + static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); + static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); + static List get targetPlatforms => + _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); + + // CMAKE + static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); + + static String _getEnv(String key) { + final res = Platform.environment[key]; + if (res == null) { + throw Exception("Missing environment variable $key"); + } + return res; + } + + static String _getEnvPath(String key) { + final res = _getEnv(key); + if (Directory(res).existsSync()) { + return res.resolveSymlink(); + } else { + return res; + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/logging.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/logging.dart new file mode 100644 index 0000000..5edd4fd --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/logging.dart @@ -0,0 +1,52 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; + +const String kSeparator = "--"; +const String kDoubleSeparator = "=="; + +bool _lastMessageWasSeparator = false; + +void _log(LogRecord rec) { + final prefix = '${rec.level.name}: '; + final out = rec.level == Level.SEVERE ? stderr : stdout; + if (rec.message == kSeparator) { + if (!_lastMessageWasSeparator) { + out.write(prefix); + out.writeln('-' * 80); + _lastMessageWasSeparator = true; + } + return; + } else if (rec.message == kDoubleSeparator) { + out.write(prefix); + out.writeln('=' * 80); + _lastMessageWasSeparator = true; + return; + } + out.write(prefix); + out.writeln(rec.message); + _lastMessageWasSeparator = false; +} + +void initLogging() { + Logger.root.level = Level.INFO; + Logger.root.onRecord.listen((LogRecord rec) { + final lines = rec.message.split('\n'); + for (final line in lines) { + if (line.isNotEmpty || lines.length == 1 || line != lines.last) { + _log(LogRecord( + rec.level, + line, + rec.loggerName, + )); + } + } + }); +} + +void enableVerboseLogging() { + Logger.root.level = Level.ALL; +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/options.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/options.dart new file mode 100644 index 0000000..22aef1d --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/options.dart @@ -0,0 +1,309 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; +import 'package:yaml/yaml.dart'; + +import 'builder.dart'; +import 'environment.dart'; +import 'rustup.dart'; + +final _log = Logger('options'); + +/// A class for exceptions that have source span information attached. +class SourceSpanException implements Exception { + // This is a getter so that subclasses can override it. + /// A message describing the exception. + String get message => _message; + final String _message; + + // This is a getter so that subclasses can override it. + /// The span associated with this exception. + /// + /// This may be `null` if the source location can't be determined. + SourceSpan? get span => _span; + final SourceSpan? _span; + + SourceSpanException(this._message, this._span); + + /// Returns a string representation of `this`. + /// + /// [color] may either be a [String], a [bool], or `null`. If it's a string, + /// it indicates an ANSI terminal color escape that should be used to + /// highlight the span's text. If it's `true`, it indicates that the text + /// should be highlighted using the default color. If it's `false` or `null`, + /// it indicates that the text shouldn't be highlighted. + @override + String toString({Object? color}) { + if (span == null) return message; + return 'Error on ${span!.message(message, color: color)}'; + } +} + +enum Toolchain { + stable, + beta, + nightly, +} + +class CargoBuildOptions { + final Toolchain toolchain; + final List flags; + + CargoBuildOptions({ + required this.toolchain, + required this.flags, + }); + + static Toolchain _toolchainFromNode(YamlNode node) { + if (node case YamlScalar(value: String name)) { + final toolchain = + Toolchain.values.firstWhereOrNull((element) => element.name == name); + if (toolchain != null) { + return toolchain; + } + } + throw SourceSpanException( + 'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.', + node.span); + } + + static CargoBuildOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + Toolchain toolchain = Toolchain.stable; + List flags = []; + for (final MapEntry(:key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: 'toolchain')) { + toolchain = _toolchainFromNode(value); + } else if (key case YamlScalar(value: 'extra_flags')) { + if (value case YamlList(nodes: List list)) { + if (list.every((element) { + if (element case YamlScalar(value: String _)) { + return true; + } + return false; + })) { + flags = list.map((e) => e.value as String).toList(); + continue; + } + } + throw SourceSpanException( + 'Extra flags must be a list of strings', value.span); + } else { + throw SourceSpanException( + 'Unknown cargo option type. Must be "toolchain" or "extra_flags".', + key.span); + } + } + return CargoBuildOptions(toolchain: toolchain, flags: flags); + } +} + +extension on YamlMap { + /// Map that extracts keys so that we can do map case check on them. + Map get valueMap => + nodes.map((key, value) => MapEntry(key.value, value)); +} + +class PrecompiledBinaries { + final String uriPrefix; + final PublicKey publicKey; + + PrecompiledBinaries({ + required this.uriPrefix, + required this.publicKey, + }); + + static PublicKey _publicKeyFromHex(String key, SourceSpan? span) { + final bytes = HEX.decode(key); + if (bytes.length != 32) { + throw SourceSpanException( + 'Invalid public key. Must be 32 bytes long.', span); + } + return PublicKey(bytes); + } + + static PrecompiledBinaries parse(YamlNode node) { + if (node case YamlMap(valueMap: Map map)) { + if (map + case { + 'url_prefix': YamlNode urlPrefixNode, + 'public_key': YamlNode publicKeyNode, + }) { + final urlPrefix = switch (urlPrefixNode) { + YamlScalar(value: String urlPrefix) => urlPrefix, + _ => throw SourceSpanException( + 'Invalid URL prefix value.', urlPrefixNode.span), + }; + final publicKey = switch (publicKeyNode) { + YamlScalar(value: String publicKey) => + _publicKeyFromHex(publicKey, publicKeyNode.span), + _ => throw SourceSpanException( + 'Invalid public key value.', publicKeyNode.span), + }; + return PrecompiledBinaries( + uriPrefix: urlPrefix, + publicKey: publicKey, + ); + } + } + throw SourceSpanException( + 'Invalid precompiled binaries value. ' + 'Expected Map with "url_prefix" and "public_key".', + node.span); + } +} + +/// Cargokit options specified for Rust crate. +class CargokitCrateOptions { + CargokitCrateOptions({ + this.cargo = const {}, + this.precompiledBinaries, + }); + + final Map cargo; + final PrecompiledBinaries? precompiledBinaries; + + static CargokitCrateOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + final options = {}; + PrecompiledBinaries? precompiledBinaries; + + for (final entry in node.nodes.entries) { + if (entry + case MapEntry( + key: YamlScalar(value: 'cargo'), + value: YamlNode node, + )) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: String name)) { + final configuration = BuildConfiguration.values + .firstWhereOrNull((element) => element.name == name); + if (configuration != null) { + options[configuration] = CargoBuildOptions.parse(value); + continue; + } + } + throw SourceSpanException( + 'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.', + key.span); + } + } else if (entry.key case YamlScalar(value: 'precompiled_binaries')) { + precompiledBinaries = PrecompiledBinaries.parse(entry.value); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".', + entry.key.span); + } + } + return CargokitCrateOptions( + cargo: options, + precompiledBinaries: precompiledBinaries, + ); + } + + static CargokitCrateOptions load({ + required String manifestDir, + }) { + final uri = Uri.file(path.join(manifestDir, "cargokit.yaml")); + final file = File.fromUri(uri); + if (file.existsSync()) { + final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri); + return parse(contents); + } else { + return CargokitCrateOptions(); + } + } +} + +class CargokitUserOptions { + // When Rustup is installed always build locally unless user opts into + // using precompiled binaries. + static bool defaultUsePrecompiledBinaries() { + return Rustup.executablePath() == null; + } + + CargokitUserOptions({ + required this.usePrecompiledBinaries, + required this.verboseLogging, + }); + + CargokitUserOptions._() + : usePrecompiledBinaries = defaultUsePrecompiledBinaries(), + verboseLogging = false; + + static CargokitUserOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + bool usePrecompiledBinaries = defaultUsePrecompiledBinaries(); + bool verboseLogging = false; + + for (final entry in node.nodes.entries) { + if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) { + if (entry.value case YamlScalar(value: bool value)) { + usePrecompiledBinaries = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "use_precompiled_binaries". Must be a boolean.', + entry.value.span); + } else if (entry.key case YamlScalar(value: 'verbose_logging')) { + if (entry.value case YamlScalar(value: bool value)) { + verboseLogging = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "verbose_logging". Must be a boolean.', + entry.value.span); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".', + entry.key.span); + } + } + return CargokitUserOptions( + usePrecompiledBinaries: usePrecompiledBinaries, + verboseLogging: verboseLogging, + ); + } + + static CargokitUserOptions load() { + String fileName = "cargokit_options.yaml"; + var userProjectDir = Directory(Environment.rootProjectDir); + + while (userProjectDir.parent.path != userProjectDir.path) { + final configFile = File(path.join(userProjectDir.path, fileName)); + if (configFile.existsSync()) { + final contents = loadYamlNode( + configFile.readAsStringSync(), + sourceUrl: configFile.uri, + ); + final res = parse(contents); + if (res.verboseLogging) { + _log.info('Found user options file at ${configFile.path}'); + } + return res; + } + userProjectDir = userProjectDir.parent; + } + return CargokitUserOptions._(); + } + + final bool usePrecompiledBinaries; + final bool verboseLogging; +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart new file mode 100644 index 0000000..019859c --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/precompile_binaries.dart @@ -0,0 +1,205 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; + +final _log = Logger('precompile_binaries'); + +class PrecompileBinaries { + PrecompileBinaries({ + required this.privateKey, + required this.githubToken, + required this.repositorySlug, + required this.manifestDir, + required this.targets, + this.androidSdkLocation, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.tempDir, + this.glibcVersion, + }); + + final PrivateKey privateKey; + final String githubToken; + final RepositorySlug repositorySlug; + final String manifestDir; + final List targets; + final String? androidSdkLocation; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? tempDir; + final String? glibcVersion; + + static String fileName(Target target, String name) { + return '${target.rust}_$name'; + } + + static String signatureFileName(Target target, String name) { + return '${target.rust}_$name.sig'; + } + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final targets = List.of(this.targets); + if (targets.isEmpty) { + targets.addAll([ + ...Target.buildableTargets(), + if (androidSdkLocation != null) ...Target.androidTargets(), + ]); + } + + _log.info('Precompiling binaries for $targets'); + + final hash = CrateHash.compute(manifestDir); + _log.info('Computed crate hash: $hash'); + + final String tagName = 'precompiled_$hash'; + + final github = GitHub(auth: Authentication.withToken(githubToken)); + final repo = github.repositories; + final release = await _getOrCreateRelease( + repo: repo, + tagName: tagName, + packageName: crateInfo.packageName, + hash: hash, + ); + + final tempDir = this.tempDir != null + ? Directory(this.tempDir!) + : Directory.systemTemp.createTempSync('precompiled_'); + + tempDir.createSync(recursive: true); + + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + + final buildEnvironment = BuildEnvironment( + configuration: BuildConfiguration.release, + crateOptions: crateOptions, + targetTempDir: tempDir.path, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: androidSdkLocation != null, + androidSdkPath: androidSdkLocation, + androidNdkVersion: androidNdkVersion, + androidMinSdkVersion: androidMinSdkVersion, + glibcVersion: glibcVersion, + ); + + final rustup = Rustup(); + + for (final target in targets) { + final artifactNames = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + if (artifactNames.every((name) { + final fileName = PrecompileBinaries.fileName(target, name); + return (release.assets ?? []).any((e) => e.name == fileName); + })) { + _log.info("All artifacts for $target already exist - skipping"); + continue; + } + + _log.info('Building for $target'); + + final builder = + RustBuilder(target: target, environment: buildEnvironment); + builder.prepare(rustup); + final res = await builder.build(); + + final assets = []; + for (final name in artifactNames) { + final file = File(path.join(res, name)); + if (!file.existsSync()) { + throw Exception('Missing artifact: ${file.path}'); + } + + final data = file.readAsBytesSync(); + final create = CreateReleaseAsset( + name: PrecompileBinaries.fileName(target, name), + contentType: "application/octet-stream", + assetData: data, + ); + final signature = sign(privateKey, data); + final signatureCreate = CreateReleaseAsset( + name: signatureFileName(target, name), + contentType: "application/octet-stream", + assetData: signature, + ); + bool verified = verify(public(privateKey), data, signature); + if (!verified) { + throw Exception('Signature verification failed'); + } + assets.add(create); + assets.add(signatureCreate); + } + _log.info('Uploading assets: ${assets.map((e) => e.name)}'); + for (final asset in assets) { + // This seems to be failing on CI so do it one by one + int retryCount = 0; + while (true) { + try { + await repo.uploadReleaseAssets(release, [asset]); + break; + } on Exception catch (e) { + if (retryCount == 10) { + rethrow; + } + ++retryCount; + _log.shout( + 'Upload failed (attempt $retryCount, will retry): ${e.toString()}'); + await Future.delayed(Duration(seconds: 2)); + } + } + } + } + + _log.info('Cleaning up'); + tempDir.deleteSync(recursive: true); + } + + Future _getOrCreateRelease({ + required RepositoriesService repo, + required String tagName, + required String packageName, + required String hash, + }) async { + Release release; + try { + _log.info('Fetching release $tagName'); + release = await repo.getReleaseByTagName(repositorySlug, tagName); + } on ReleaseNotFound { + _log.info('Release not found - creating release $tagName'); + release = await repo.createRelease( + repositorySlug, + CreateRelease.from( + tagName: tagName, + name: 'Precompiled binaries ${hash.substring(0, 8)}', + targetCommitish: null, + isDraft: false, + isPrerelease: false, + body: 'Precompiled binaries for crate $packageName, ' + 'crate hash $hash.', + )); + } + return release; + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/rustup.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/rustup.dart new file mode 100644 index 0000000..e46722b --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/rustup.dart @@ -0,0 +1,149 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; + +import 'util.dart'; + +class _Toolchain { + _Toolchain( + this.name, + this.targets, + ); + + final String name; + final List targets; +} + +class Rustup { + List? installedTargets(String toolchain) { + final targets = _installedTargets(toolchain); + return targets != null ? List.unmodifiable(targets) : null; + } + + void installToolchain(String toolchain) { + log.info("Installing Rust toolchain: $toolchain"); + runCommand("rustup", ['toolchain', 'install', toolchain]); + _installedToolchains + .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); + } + + void installTarget( + String target, { + required String toolchain, + }) { + log.info("Installing Rust target: $target"); + runCommand("rustup", ['target', 'add', '--toolchain', toolchain, target]); + _installedTargets(toolchain)?.add(target); + } + + bool _didInstallZigBuild = false; + + void installZigBuild(String toolchain) { + if (_didInstallZigBuild) { + return; + } + + log.info("Installing Zig build"); + runCommand("rustup", [ + 'run', + toolchain, + 'cargo', + 'install', + '--locked', + 'cargo-zigbuild', + ]); + _didInstallZigBuild = true; + } + + final List<_Toolchain> _installedToolchains; + + Rustup() : _installedToolchains = _getInstalledToolchains(); + + List? _installedTargets(String toolchain) => _installedToolchains + .firstWhereOrNull( + (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) + ?.targets; + + static List<_Toolchain> _getInstalledToolchains() { + String extractToolchainName(String line) { + // ignore (default) after toolchain name + final parts = line.split(' '); + return parts[0]; + } + + final res = runCommand("rustup", ['toolchain', 'list']); + + // To list all non-custom toolchains, we need to filter out lines that + // don't start with "stable", "beta", or "nightly". + Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) + .map(extractToolchainName) + .toList(growable: true); + + return lines + .map( + (name) => _Toolchain( + name, + _getInstalledTargets(name), + ), + ) + .toList(growable: true); + } + + static List _getInstalledTargets(String toolchain) { + final res = runCommand("rustup", [ + 'target', + 'list', + '--toolchain', + toolchain, + '--installed', + ]); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty) + .toList(growable: true); + return lines; + } + + bool _didInstallRustSrcForNightly = false; + + void installRustSrcForNightly() { + if (_didInstallRustSrcForNightly) { + return; + } + // Useful for -Z build-std + runCommand( + "rustup", + ['component', 'add', 'rust-src', '--toolchain', 'nightly'], + ); + _didInstallRustSrcForNightly = true; + } + + static String? executablePath() { + final envPath = Platform.environment['PATH']; + final envPathSeparator = Platform.isWindows ? ';' : ':'; + final home = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + final paths = [ + if (home != null) path.join(home, '.cargo', 'bin'), + if (envPath != null) ...envPath.split(envPathSeparator), + ]; + for (final p in paths) { + final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; + final rustupPath = path.join(p, rustup); + if (File(rustupPath).existsSync()) { + return rustupPath; + } + } + return null; + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/target.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/target.dart new file mode 100644 index 0000000..624504e --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/target.dart @@ -0,0 +1,147 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; + +import 'util.dart'; + +class Target { + Target({ + required this.rust, + this.flutter, + this.android, + this.androidMinSdkVersion, + this.darwinPlatform, + this.darwinArch, + }); + + static final all = [ + Target( + rust: 'armv7-linux-androideabi', + flutter: 'android-arm', + android: 'armeabi-v7a', + androidMinSdkVersion: 16, + ), + Target( + rust: 'aarch64-linux-android', + flutter: 'android-arm64', + android: 'arm64-v8a', + androidMinSdkVersion: 21, + ), + Target( + rust: 'i686-linux-android', + flutter: 'android-x86', + android: 'x86', + androidMinSdkVersion: 16, + ), + Target( + rust: 'x86_64-linux-android', + flutter: 'android-x64', + android: 'x86_64', + androidMinSdkVersion: 21, + ), + Target( + rust: 'x86_64-pc-windows-msvc', + flutter: 'windows-x64', + ), + Target( + rust: 'aarch64-pc-windows-msvc', + flutter: 'windows-arm64', + ), + Target( + rust: 'x86_64-unknown-linux-gnu', + flutter: 'linux-x64', + ), + Target( + rust: 'aarch64-unknown-linux-gnu', + flutter: 'linux-arm64', + ), + Target(rust: 'riscv64gc-unknown-linux-gnu', flutter: 'linux-riscv64'), + Target( + rust: 'x86_64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'x86_64', + ), + Target( + rust: 'aarch64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios', + darwinPlatform: 'iphoneos', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios-sim', + darwinPlatform: 'iphonesimulator', + darwinArch: 'arm64', + ), + Target( + rust: 'x86_64-apple-ios', + darwinPlatform: 'iphonesimulator', + darwinArch: 'x86_64', + ), + ]; + + static Target? forFlutterName(String flutterName) { + return all.firstWhereOrNull((element) => element.flutter == flutterName); + } + + static Target? forDarwin({ + required String platformName, + required String darwinAarch, + }) { + return all.firstWhereOrNull((element) => // + element.darwinPlatform == platformName && + element.darwinArch == darwinAarch); + } + + static Target? forRustTriple(String triple) { + return all.firstWhereOrNull((element) => element.rust == triple); + } + + static List androidTargets() { + return all + .where((element) => element.android != null) + .toList(growable: false); + } + + /// Returns buildable targets on current host platform ignoring Android targets. + static List buildableTargets() { + if (Platform.isLinux) { + // Right now we don't support cross-compiling on Linux. So we just return + // the host target. + final arch = (runCommand('arch', []).stdout as String).trim(); + if (arch == 'aarch64') { + return [Target.forRustTriple('aarch64-unknown-linux-gnu')!]; + } else if (arch == 'riscv64') { + return [Target.forRustTriple('riscv64gc-unknown-linux-gnu')!]; + } else { + return [Target.forRustTriple('x86_64-unknown-linux-gnu')!]; + } + } + return all.where((target) { + if (Platform.isWindows) { + return target.rust.contains('-windows-'); + } else if (Platform.isMacOS) { + return target.darwinPlatform != null; + } + return false; + }).toList(growable: false); + } + + @override + String toString() { + return rust; + } + + final String? flutter; + final String rust; + final String? android; + final int? androidMinSdkVersion; + final String? darwinPlatform; + final String? darwinArch; +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/util.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/util.dart new file mode 100644 index 0000000..8bb6a87 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/util.dart @@ -0,0 +1,172 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'logging.dart'; +import 'rustup.dart'; + +final log = Logger("process"); + +class CommandFailedException implements Exception { + final String executable; + final List arguments; + final ProcessResult result; + + CommandFailedException({ + required this.executable, + required this.arguments, + required this.result, + }); + + @override + String toString() { + final stdout = result.stdout.toString().trim(); + final stderr = result.stderr.toString().trim(); + return [ + "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", + "Returned Exit Code: ${result.exitCode}", + kSeparator, + "STDOUT:", + if (stdout.isNotEmpty) stdout, + kSeparator, + "STDERR:", + if (stderr.isNotEmpty) stderr, + ].join('\n'); + } +} + +class TestRunCommandArgs { + final String executable; + final List arguments; + final String? workingDirectory; + final Map? environment; + final bool includeParentEnvironment; + final bool runInShell; + final Encoding? stdoutEncoding; + final Encoding? stderrEncoding; + + TestRunCommandArgs({ + required this.executable, + required this.arguments, + this.workingDirectory, + this.environment, + this.includeParentEnvironment = true, + this.runInShell = false, + this.stdoutEncoding, + this.stderrEncoding, + }); +} + +class TestRunCommandResult { + TestRunCommandResult({ + this.pid = 1, + this.exitCode = 0, + this.stdout = '', + this.stderr = '', + }); + + final int pid; + final int exitCode; + final String stdout; + final String stderr; +} + +TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride; + +ProcessResult runCommand( + String executable, + List arguments, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, +}) { + if (testRunCommandOverride != null) { + final result = testRunCommandOverride!(TestRunCommandArgs( + executable: executable, + arguments: arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stdoutEncoding: stdoutEncoding, + stderrEncoding: stderrEncoding, + )); + return ProcessResult( + result.pid, + result.exitCode, + result.stdout, + result.stderr, + ); + } + log.finer('Running command $executable ${arguments.join(' ')}'); + final res = Process.runSync( + _resolveExecutable(executable), + arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stderrEncoding: stderrEncoding, + stdoutEncoding: stdoutEncoding, + ); + if (res.exitCode != 0) { + throw CommandFailedException( + executable: executable, + arguments: arguments, + result: res, + ); + } else { + return res; + } +} + +class RustupNotFoundException implements Exception { + @override + String toString() { + return [ + ' ', + 'rustup not found in PATH.', + ' ', + 'Maybe you need to install Rust? It only takes a minute:', + ' ', + if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', + if (hasHomebrewRustInPath()) ...[ + '\$ brew unlink rust # Unlink homebrew Rust from PATH', + ], + if (!Platform.isWindows) + "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", + ' ', + ].join('\n'); + } + + static bool hasHomebrewRustInPath() { + if (!Platform.isMacOS) { + return false; + } + final envPath = Platform.environment['PATH'] ?? ''; + final paths = envPath.split(':'); + return paths.any((p) { + return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); + }); + } +} + +String _resolveExecutable(String executable) { + if (executable == 'rustup') { + final resolved = Rustup.executablePath(); + if (resolved != null) { + return resolved; + } + throw RustupNotFoundException(); + } else { + return executable; + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart b/useragent/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart new file mode 100644 index 0000000..2366b57 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart @@ -0,0 +1,84 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; + +import 'artifacts_provider.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; + +class VerifyBinaries { + VerifyBinaries({ + required this.manifestDir, + }); + + final String manifestDir; + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final config = CargokitCrateOptions.load(manifestDir: manifestDir); + final precompiledBinaries = config.precompiledBinaries; + if (precompiledBinaries == null) { + stdout.writeln('Crate does not support precompiled binaries.'); + } else { + final crateHash = CrateHash.compute(manifestDir); + stdout.writeln('Crate hash: $crateHash'); + + for (final target in Target.all) { + final message = 'Checking ${target.rust}...'; + stdout.write(message.padRight(40)); + stdout.flush(); + + final artifacts = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + final prefix = precompiledBinaries.uriPrefix; + + bool ok = true; + + for (final artifact in artifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = + Uri.parse('$prefix$crateHash/$signatureFileName'); + + final signature = await get(signatureUrl); + if (signature.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + final asset = await get(url); + if (asset.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + + if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, + signature.bodyBytes)) { + stdout.writeln('INVALID SIGNATURE'); + ok = false; + } + } + + if (ok) { + stdout.writeln('OK'); + } + } + } + } +} diff --git a/useragent/rust_builder/cargokit/build_tool/pubspec.lock b/useragent/rust_builder/cargokit/build_tool/pubspec.lock new file mode 100644 index 0000000..343bdd3 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/pubspec.lock @@ -0,0 +1,453 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: "direct main" + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: "direct main" + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + ed25519_edwards: + dependency: "direct main" + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + github: + dependency: "direct main" + description: + name: github + sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411" + url: "https://pub.dev" + source: hosted + version: "9.17.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: "direct main" + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + url: "https://pub.dev" + source: hosted + version: "1.24.6" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + url: "https://pub.dev" + source: hosted + version: "0.5.6" + toml: + dependency: "direct main" + description: + name: toml + sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" + url: "https://pub.dev" + source: hosted + version: "0.14.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + version: + dependency: "direct main" + description: + name: version + sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + url: "https://pub.dev" + source: hosted + version: "11.9.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/useragent/rust_builder/cargokit/build_tool/pubspec.yaml b/useragent/rust_builder/cargokit/build_tool/pubspec.yaml new file mode 100644 index 0000000..18c61e3 --- /dev/null +++ b/useragent/rust_builder/cargokit/build_tool/pubspec.yaml @@ -0,0 +1,33 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +name: build_tool +description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. +publish_to: none +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + +# Add regular dependencies here. +dependencies: + # these are pinned on purpose because the bundle_tool_runner doesn't have + # pubspec.lock. See run_build_tool.sh + logging: 1.2.0 + path: 1.8.0 + version: 3.0.0 + collection: 1.18.0 + ed25519_edwards: 0.3.1 + hex: 0.2.0 + yaml: 3.1.2 + source_span: 1.10.0 + github: 9.17.0 + args: 2.4.2 + crypto: 3.0.3 + convert: 3.1.1 + http: 1.1.0 + toml: 0.14.0 + +dev_dependencies: + lints: ^2.1.0 + test: ^1.24.0 diff --git a/useragent/rust_builder/cargokit/cmake/cargokit.cmake b/useragent/rust_builder/cargokit/cmake/cargokit.cmake new file mode 100644 index 0000000..ddd05df --- /dev/null +++ b/useragent/rust_builder/cargokit/cmake/cargokit.cmake @@ -0,0 +1,99 @@ +SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH) + +if(WIN32) + # REALPATH does not properly resolve symlinks on windows :-/ + execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Arguments +# - target: CMAKE target to which rust library is linked +# - manifest_dir: relative path from current folder to directory containing cargo manifest +# - lib_name: cargo package name +# - any_symbol_name: name of any exported symbol from the library. +# used on windows to force linking with library. +function(apply_cargokit target manifest_dir lib_name any_symbol_name) + + set(CARGOKIT_LIB_NAME "${lib_name}") + set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}") + if (CMAKE_CONFIGURATION_TYPES) + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${CARGOKIT_LIB_FULL_NAME}") + else() + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}") + endif() + set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build") + + if (FLUTTER_TARGET_PLATFORM) + set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}") + else() + set(CARGOKIT_TARGET_PLATFORM "windows-x64") + endif() + + set(CARGOKIT_ENV + "CARGOKIT_CMAKE=${CMAKE_COMMAND}" + "CARGOKIT_CONFIGURATION=$" + "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" + "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" + "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" + "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" + "CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool" + "CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}" + ) + + if (WIN32) + set(SCRIPT_EXTENSION ".cmd") + set(IMPORT_LIB_EXTENSION ".lib") + else() + set(SCRIPT_EXTENSION ".sh") + set(IMPORT_LIB_EXTENSION "") + execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}") + endif() + + # Using generators in custom command is only supported in CMake 3.20+ + if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0") + foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) + add_custom_command( + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}" + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endforeach() + else() + add_custom_command( + OUTPUT + ${OUTPUT_LIB} + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endif() + + + set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE) + + if (TARGET ${target}) + # If we have actual cmake target provided create target and make existing + # target depend on it + add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB}) + add_dependencies("${target}" "${target}_cargokit") + target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}") + if(WIN32) + target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}") + endif() + else() + # Otherwise (FFI) just use ALL to force building always + add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB}) + endif() + + # Allow adding the output library to plugin bundled libraries + set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE) + +endfunction() diff --git a/useragent/rust_builder/cargokit/cmake/resolve_symlinks.ps1 b/useragent/rust_builder/cargokit/cmake/resolve_symlinks.ps1 new file mode 100644 index 0000000..2ac593a --- /dev/null +++ b/useragent/rust_builder/cargokit/cmake/resolve_symlinks.ps1 @@ -0,0 +1,34 @@ +function Resolve-Symlinks { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [string] $Path + ) + + [string] $separator = '/' + [string[]] $parts = $Path.Split($separator) + + [string] $realPath = '' + foreach ($part in $parts) { + if ($realPath -and !$realPath.EndsWith($separator)) { + $realPath += $separator + } + + $realPath += $part.Replace('\', '/') + + # The slash is important when using Get-Item on Drive letters in pwsh. + if (-not($realPath.Contains($separator)) -and $realPath.EndsWith(':')) { + $realPath += '/' + } + + $item = Get-Item $realPath + if ($item.LinkTarget) { + $realPath = $item.LinkTarget.Replace('\', '/') + } + } + $realPath +} + +$path = Resolve-Symlinks -Path $args[0] +Write-Host $path diff --git a/useragent/rust_builder/cargokit/gradle/plugin.gradle b/useragent/rust_builder/cargokit/gradle/plugin.gradle new file mode 100644 index 0000000..68ff649 --- /dev/null +++ b/useragent/rust_builder/cargokit/gradle/plugin.gradle @@ -0,0 +1,184 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import java.nio.file.Paths +import org.apache.tools.ant.taskdefs.condition.Os + +CargoKitPlugin.file = buildscript.sourceFile + +apply plugin: CargoKitPlugin + +class CargoKitExtension { + String manifestDir; // Relative path to folder containing Cargo.toml + String libname; // Library name within Cargo.toml. Must be a cdylib +} + +abstract class CargoKitBuildTask extends DefaultTask { + + @Input + String buildMode + + @Input + String buildDir + + @Input + String outputDir + + @Input + String ndkVersion + + @Input + String sdkDirectory + + @Input + int compileSdkVersion; + + @Input + int minSdkVersion; + + @Input + String pluginFile + + @Input + List targetPlatforms + + @TaskAction + def build() { + if (project.cargokit.manifestDir == null) { + throw new GradleException("Property 'manifestDir' must be set on cargokit extension"); + } + + if (project.cargokit.libname == null) { + throw new GradleException("Property 'libname' must be set on cargokit extension"); + } + + def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" + def path = Paths.get(new File(pluginFile).parent, "..", executableName); + + def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) + + def rootProjectDir = project.rootProject.projectDir + + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + project.exec { + commandLine 'chmod', '+x', path + } + } + + project.exec { + executable path + args "build-gradle" + environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir + environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool" + environment "CARGOKIT_MANIFEST_DIR", manifestDir + environment "CARGOKIT_CONFIGURATION", buildMode + environment "CARGOKIT_TARGET_TEMP_DIR", buildDir + environment "CARGOKIT_OUTPUT_DIR", outputDir + environment "CARGOKIT_NDK_VERSION", ndkVersion + environment "CARGOKIT_SDK_DIR", sdkDirectory + environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion + environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion + environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",") + environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] + } + } +} + +class CargoKitPlugin implements Plugin { + + static String file; + + private Plugin findFlutterPlugin(Project rootProject) { + _findFlutterPlugin(rootProject.childProjects) + } + + private Plugin _findFlutterPlugin(Map projects) { + for (project in projects) { + for (plugin in project.value.getPlugins()) { + if (plugin.class.name == "com.flutter.gradle.FlutterPlugin" || plugin.class.name == "FlutterPlugin") { + return plugin; + } + } + def plugin = _findFlutterPlugin(project.value.childProjects); + if (plugin != null) { + return plugin; + } + } + return null; + } + + @Override + void apply(Project project) { + def plugin = findFlutterPlugin(project.rootProject); + + project.extensions.create("cargokit", CargoKitExtension) + + if (plugin == null) { + print("Flutter plugin not found, CargoKit plugin will not be applied.") + return; + } + + def cargoBuildDir = "${project.buildDir}/build" + + // Determine if the project is an application or library + def isApplication = plugin.project.plugins.hasPlugin('com.android.application') + def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants + + variants.all { variant -> + + final buildType = variant.buildType.name + + def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; + def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; + jniLibs.srcDir(new File(cargoOutputDir)) + + def List platforms + try { + platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect() + } catch (Exception ignored) { + platforms = plugin.getTargetPlatforms().collect() + } + + // Same thing addFlutterDependencies does in flutter.gradle + if (buildType == "debug") { + platforms.add("android-x86") + platforms.add("android-x64") + } + + // The task name depends on plugin properties, which are not available + // at this point + project.getGradle().afterProject { + def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}"; + + if (project.tasks.findByName(taskName)) { + return + } + + if (plugin.project.android.ndkVersion == null) { + throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") + } + + def task = project.tasks.create(taskName, CargoKitBuildTask.class) { + buildMode = variant.buildType.name + buildDir = cargoBuildDir + outputDir = cargoOutputDir + ndkVersion = plugin.project.android.ndkVersion + sdkDirectory = plugin.project.android.sdkDirectory + minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int + compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int + targetPlatforms = platforms + pluginFile = CargoKitPlugin.file + } + def onTask = { newTask -> + if (newTask.name == "merge${buildType.capitalize()}NativeLibs") { + newTask.dependsOn task + // Fix gradle 7.4.2 not picking up JNI library changes + newTask.outputs.upToDateWhen { false } + } + } + project.tasks.each onTask + project.tasks.whenTaskAdded onTask + } + } + } +} diff --git a/useragent/rust_builder/cargokit/run_build_tool.cmd b/useragent/rust_builder/cargokit/run_build_tool.cmd new file mode 100755 index 0000000..c45d0aa --- /dev/null +++ b/useragent/rust_builder/cargokit/run_build_tool.cmd @@ -0,0 +1,91 @@ +@echo off +setlocal + +setlocal ENABLEDELAYEDEXPANSION + +SET BASEDIR=%~dp0 + +if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( + mkdir "%CARGOKIT_TOOL_TEMP_DIR%" +) +cd /D "%CARGOKIT_TOOL_TEMP_DIR%" + +SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool +SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart + +set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% + +( + echo name: build_tool_runner + echo version: 1.0.0 + echo publish_to: none + echo. + echo environment: + echo sdk: '^>=3.0.0 ^<4.0.0' + echo. + echo dependencies: + echo build_tool: + echo path: %BUILD_TOOL_PKG_DIR_POSIX% +) >pubspec.yaml + +if not exist bin ( + mkdir bin +) + +( + echo import 'package:build_tool/build_tool.dart' as build_tool; + echo void main^(List^ args^) ^{ + echo build_tool.runMain^(args^); + echo ^} +) >bin\build_tool_runner.dart + +SET PRECOMPILED=bin\build_tool_runner.dill + +REM To detect changes in package we compare output of DIR /s (recursive) +set PREV_PACKAGE_INFO=.dart_tool\package_info.prev +set CUR_PACKAGE_INFO=.dart_tool\package_info.cur + +DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" + +REM Last line in dir output is free space on harddrive. That is bound to +REM change between invocation so we need to remove it +( + Set "Line=" + For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( + SetLocal EnableDelayedExpansion + If Defined Line Echo !Line! + EndLocal + Set "Line=%%A") +) >"%CUR_PACKAGE_INFO%" +DEL "%CUR_PACKAGE_INFO%_orig" + +REM Compare current directory listing with previous +FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 + +If %ERRORLEVEL% neq 0 ( + REM Changed - copy current to previous and remove precompiled kernel + if exist "%PREV_PACKAGE_INFO%" ( + DEL "%PREV_PACKAGE_INFO%" + ) + MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" + if exist "%PRECOMPILED%" ( + DEL "%PRECOMPILED%" + ) +) + +REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% +REM which means we need to do pub get and precompile +if not exist "%PRECOMPILED%" ( + echo Running pub get in "%cd%" + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart +) + +"%DART%" "%PRECOMPILED%" %* + +REM 253 means invalid snapshot version. +If %ERRORLEVEL% equ 253 ( + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart + "%DART%" "%PRECOMPILED%" %* +) diff --git a/useragent/rust_builder/cargokit/run_build_tool.sh b/useragent/rust_builder/cargokit/run_build_tool.sh new file mode 100755 index 0000000..24b0ed8 --- /dev/null +++ b/useragent/rust_builder/cargokit/run_build_tool.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +set -e + +BASEDIR=$(dirname "$0") + +mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" + +cd "$CARGOKIT_TOOL_TEMP_DIR" + +# Write a very simple bin package in temp folder that depends on build_tool package +# from Cargokit. This is done to ensure that we don't pollute Cargokit folder +# with .dart_tool contents. + +BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" + +if [[ -z $FLUTTER_ROOT ]]; then # not defined + DART=dart +else + DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" +fi + +cat << EOF > "pubspec.yaml" +name: build_tool_runner +version: 1.0.0 +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + build_tool: + path: "$BUILD_TOOL_PKG_DIR" +EOF + +mkdir -p "bin" + +cat << EOF > "bin/build_tool_runner.dart" +import 'package:build_tool/build_tool.dart' as build_tool; +void main(List args) { + build_tool.runMain(args); +} +EOF + +# Create alias for `shasum` if it does not exist and `sha1sum` exists +if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then + shopt -s expand_aliases + alias shasum="sha1sum" +fi + +# Dart run will not cache any package that has a path dependency, which +# is the case for our build_tool_runner. So instead we precompile the package +# ourselves. +# To invalidate the cached kernel we use the hash of ls -LR of the build_tool +# package directory. This should be good enough, as the build_tool package +# itself is not meant to have any path dependencies. + +if [[ "$OSTYPE" == "darwin"* ]]; then + PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) +else + PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) +fi + +PACKAGE_HASH_FILE=".package_hash" + +if [ -f "$PACKAGE_HASH_FILE" ]; then + EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") + if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then + rm "$PACKAGE_HASH_FILE" + fi +fi + +# Run pub get if needed. +if [ ! -f "$PACKAGE_HASH_FILE" ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" +fi + +# Rebuild the tool if it was deleted by Android Studio +if [ ! -f "bin/build_tool_runner.dill" ]; then + "$DART" compile kernel bin/build_tool_runner.dart +fi + +set +e + +"$DART" bin/build_tool_runner.dill "$@" + +exit_code=$? + +# 253 means invalid snapshot version. +if [ $exit_code == 253 ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + "$DART" bin/build_tool_runner.dill "$@" + exit_code=$? +fi + +exit $exit_code diff --git a/useragent/rust_builder/ios/Classes/dummy_file.c b/useragent/rust_builder/ios/Classes/dummy_file.c new file mode 100644 index 0000000..e06dab9 --- /dev/null +++ b/useragent/rust_builder/ios/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/useragent/rust_builder/ios/rust_lib_arbiter.podspec b/useragent/rust_builder/ios/rust_lib_arbiter.podspec new file mode 100644 index 0000000..2e27d0b --- /dev/null +++ b/useragent/rust_builder/ios/rust_lib_arbiter.podspec @@ -0,0 +1,45 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_lib_arbiter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_lib_arbiter' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '11.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_arbiter', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_arbiter.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_arbiter.a', + } +end \ No newline at end of file diff --git a/useragent/rust_builder/linux/CMakeLists.txt b/useragent/rust_builder/linux/CMakeLists.txt new file mode 100644 index 0000000..15b5dd9 --- /dev/null +++ b/useragent/rust_builder/linux/CMakeLists.txt @@ -0,0 +1,19 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "rust_lib_arbiter") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../../rust rust_lib_arbiter "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(rust_lib_arbiter_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/useragent/rust_builder/macos/Classes/dummy_file.c b/useragent/rust_builder/macos/Classes/dummy_file.c new file mode 100644 index 0000000..e06dab9 --- /dev/null +++ b/useragent/rust_builder/macos/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/useragent/rust_builder/macos/rust_lib_arbiter.podspec b/useragent/rust_builder/macos/rust_lib_arbiter.podspec new file mode 100644 index 0000000..a623c95 --- /dev/null +++ b/useragent/rust_builder/macos/rust_lib_arbiter.podspec @@ -0,0 +1,44 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint rust_lib_arbiter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'rust_lib_arbiter' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib_arbiter', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib_arbiter.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib_arbiter.a', + } +end \ No newline at end of file diff --git a/useragent/rust_builder/pubspec.yaml b/useragent/rust_builder/pubspec.yaml new file mode 100644 index 0000000..98ebd2c --- /dev/null +++ b/useragent/rust_builder/pubspec.yaml @@ -0,0 +1,34 @@ +name: rust_lib_arbiter +description: "Utility to build Rust code" +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.3.0 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + ffi: ^2.0.2 + ffigen: ^11.0.0 + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + windows: + ffiPlugin: true diff --git a/useragent/rust_builder/windows/.gitignore b/useragent/rust_builder/windows/.gitignore new file mode 100644 index 0000000..b3eb2be --- /dev/null +++ b/useragent/rust_builder/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/useragent/rust_builder/windows/CMakeLists.txt b/useragent/rust_builder/windows/CMakeLists.txt new file mode 100644 index 0000000..7743fd1 --- /dev/null +++ b/useragent/rust_builder/windows/CMakeLists.txt @@ -0,0 +1,20 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "rust_lib_arbiter") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../../../../../../rust rust_lib_arbiter "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(rust_lib_arbiter_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/useragent/test_driver/integration_test.dart b/useragent/test_driver/integration_test.dart new file mode 100644 index 0000000..b38629c --- /dev/null +++ b/useragent/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/useragent/windows/flutter/generated_plugins.cmake b/useragent/windows/flutter/generated_plugins.cmake index d3c6423..834220c 100644 --- a/useragent/windows/flutter/generated_plugins.cmake +++ b/useragent/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + rust_lib_arbiter ) set(PLUGIN_BUNDLED_LIBRARIES) From 205227a3df9501cf1b6f86fc098344b20dfd396e Mon Sep 17 00:00:00 2001 From: hdbg Date: Wed, 8 Apr 2026 17:45:49 +0200 Subject: [PATCH 04/13] fix(server::integrity): `vault` now differentias between expected/unexpected states for commands more granularly --- server/crates/arbiter-server/src/crypto/integrity/v1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index 21efbe7..7609e2c 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -194,7 +194,7 @@ pub async fn verify_entity( Ok(false) => Err(Error::MacMismatch { entity_kind: E::KIND, }), - Err(SendError::HandlerError(vault::Error::NotBootstrapped)) => { + Err(SendError::HandlerError(vault::Error::Sealed)) => { Ok(AttestationStatus::Unavailable) } Err(_) => Err(Error::VaultSend), From 87ee0fe87bbb7546b3a3892055b8b10011b455c4 Mon Sep 17 00:00:00 2001 From: hdbg Date: Wed, 8 Apr 2026 18:29:52 +0200 Subject: [PATCH 05/13] feat(user-agent): add VaultGate for sealed vault authentication --- server/Cargo.lock | 55 +++- server/crates/arbiter-crypto/Cargo.toml | 3 +- server/crates/arbiter-crypto/src/authn/v1.rs | 8 + server/crates/arbiter-crypto/src/lib.rs | 2 + .../crates/arbiter-server/src/actors/mod.rs | 1 + .../src/actors/useragent_registry.rs | 57 ++++ .../arbiter-server/src/actors/vault/mod.rs | 8 +- server/crates/arbiter-server/src/grpc/mod.rs | 3 +- .../arbiter-server/src/grpc/user_agent.rs | 115 ++++++- .../src/grpc/user_agent/auth.rs | 4 +- .../arbiter-server/src/grpc/user_agent/evm.rs | 2 +- .../src/grpc/user_agent/sdk_client.rs | 2 +- .../src/grpc/user_agent/vault.rs | 142 +-------- .../src/grpc/user_agent/vault_gate.rs | 151 +++++++++ .../peers/user_agent/{auth.rs => auth/mod.rs} | 6 +- .../src/peers/user_agent/auth/state.rs | 150 +++++---- .../src/peers/user_agent/mod.rs | 58 ++-- .../session/{connection.rs => handlers.rs} | 227 +------------- .../user_agent/{session.rs => session/mod.rs} | 98 +----- .../src/peers/user_agent/session/state.rs | 27 -- .../src/peers/user_agent/vault_gate/mod.rs | 295 ++++++++++++++++++ .../src/peers/user_agent/vault_gate/state.rs | 18 ++ .../arbiter-server/tests/user_agent/auth.rs | 20 +- .../arbiter-server/tests/user_agent/unseal.rs | 73 +++-- 24 files changed, 900 insertions(+), 625 deletions(-) create mode 100644 server/crates/arbiter-server/src/actors/useragent_registry.rs create mode 100644 server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs rename server/crates/arbiter-server/src/peers/user_agent/{auth.rs => auth/mod.rs} (94%) rename server/crates/arbiter-server/src/peers/user_agent/session/{connection.rs => handlers.rs} (54%) rename server/crates/arbiter-server/src/peers/user_agent/{session.rs => session/mod.rs} (59%) delete mode 100644 server/crates/arbiter-server/src/peers/user_agent/session/state.rs create mode 100644 server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs create mode 100644 server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs diff --git a/server/Cargo.lock b/server/Cargo.lock index d692f5b..3787bcb 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -700,6 +700,7 @@ dependencies = [ "memsafe", "ml-dsa", "rand 0.10.0", + "x-wing", ] [[package]] @@ -779,7 +780,7 @@ dependencies = [ "tonic", "tracing", "tracing-subscriber", - "x25519-dalek", + "x25519-dalek 2.0.1", "zeroize", ] @@ -1621,6 +1622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", + "rand_core 0.10.0", ] [[package]] @@ -2598,6 +2600,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" dependencies = [ + "ctutils", "typenum", "zeroize", ] @@ -3030,6 +3033,16 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.1", + "rand_core 0.10.0", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3250,12 +3263,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ml-kem" +version = "0.3.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04437cb1a66c0b78740927b76cc61f218344b9f6ef3dd430e283274a718ef0e9" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "rand_core 0.10.0", + "sha3 0.11.0", + "zeroize", +] + [[package]] name = "module-lattice" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "164eb3faeaecbd14b0b2a917c1b4d0c035097a9c559b0bed85c2cdd032bc8faa" dependencies = [ + "ctutils", "hybrid-array", "num-traits", "zeroize", @@ -6105,6 +6133,20 @@ dependencies = [ "tap", ] +[[package]] +name = "x-wing" +version = "0.1.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d0d5f4d1f26b9b9e7477af1d3bef960e1d1fb64edab7912fde472a8a8432e" +dependencies = [ + "kem", + "ml-kem", + "rand_core 0.10.0", + "sha3 0.11.0", + "x25519-dalek 3.0.0-pre.6", + "zeroize", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -6117,6 +6159,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x25519-dalek" +version = "3.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d5d6ff67acd3945b933e592bfa7143db4fcbb2f871754b6b9fbd7847fc5aea" +dependencies = [ + "curve25519-dalek 5.0.0-pre.6", + "rand_core 0.10.0", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.18.1" diff --git a/server/crates/arbiter-crypto/Cargo.toml b/server/crates/arbiter-crypto/Cargo.toml index f5d735a..238b3db 100644 --- a/server/crates/arbiter-crypto/Cargo.toml +++ b/server/crates/arbiter-crypto/Cargo.toml @@ -8,6 +8,7 @@ ml-dsa = {workspace = true, optional = true } rand = {workspace = true, optional = true} base64 = {workspace = true, optional = true } memsafe = {version = "0.4.0", optional = true} +x-wing = { version = "0.1.0-rc.0", features = ["zeroize"] } [lints] workspace = true @@ -15,4 +16,4 @@ workspace = true [features] default = ["authn", "safecell"] authn = ["dep:ml-dsa", "dep:rand", "dep:base64"] -safecell = ["dep:memsafe"] \ No newline at end of file +safecell = ["dep:memsafe"] diff --git a/server/crates/arbiter-crypto/src/authn/v1.rs b/server/crates/arbiter-crypto/src/authn/v1.rs index 6536383..ff65104 100644 --- a/server/crates/arbiter-crypto/src/authn/v1.rs +++ b/server/crates/arbiter-crypto/src/authn/v1.rs @@ -1,3 +1,5 @@ +use std::hash::Hash; + use base64::{Engine as _, prelude::BASE64_STANDARD}; use ml_dsa::{ EncodedVerifyingKey, Error, KeyGen, MlDsa87, Seed, Signature as MlDsaSignature, @@ -17,6 +19,12 @@ pub type KeyParams = MlDsa87; #[derive(Clone, Debug, PartialEq)] pub struct PublicKey(Box>); +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.to_bytes().hash(state); + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Signature(Box>); diff --git a/server/crates/arbiter-crypto/src/lib.rs b/server/crates/arbiter-crypto/src/lib.rs index 5015af2..8d021fd 100644 --- a/server/crates/arbiter-crypto/src/lib.rs +++ b/server/crates/arbiter-crypto/src/lib.rs @@ -3,3 +3,5 @@ pub mod authn; #[cfg(feature = "safecell")] pub mod safecell; + +pub use x_wing; diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index 8b2ebd8..adb51f7 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -13,6 +13,7 @@ pub mod bootstrap; pub mod evm; pub mod flow_coordinator; pub mod vault; +pub mod useragent_registry; #[derive(Error, Debug)] pub enum SpawnError { diff --git a/server/crates/arbiter-server/src/actors/useragent_registry.rs b/server/crates/arbiter-server/src/actors/useragent_registry.rs new file mode 100644 index 0000000..3c80735 --- /dev/null +++ b/server/crates/arbiter-server/src/actors/useragent_registry.rs @@ -0,0 +1,57 @@ +use alloy::primitives::map::HashMap; +use arbiter_crypto::authn; +use kameo::{error::Infallible, prelude::*}; + +use crate::{db::DatabasePool, peers::user_agent::{Credentials, UserAgentSession}}; + +use super::vault::{Vault, events as vault_events}; + +pub struct Args { + pub vault: ActorRef, + pub pool: DatabasePool, +} + +pub struct UserAgentRegistry { + vault: ActorRef, + pool: DatabasePool, + connected: HashMap>, +} + +impl Message for UserAgentRegistry { + type Reply = (); + + async fn handle( + &mut self, + msg: vault_events::Bootstrapped, + ctx: &mut Context, + ) -> Self::Reply { + todo!() + } +} + +impl Message for UserAgentRegistry { + type Reply = (); + + async fn handle( + &mut self, + msg: vault_events::Unsealed, + ctx: &mut Context, + ) -> Self::Reply { + todo!() + } +} +impl Actor for UserAgentRegistry { + type Args = Args; + + type Error = Infallible; + + async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { + Ok(Self { + vault: args.vault, + pool: args.pool, + connected: HashMap::default(), + }) + } + + +} diff --git a/server/crates/arbiter-server/src/actors/vault/mod.rs b/server/crates/arbiter-server/src/actors/vault/mod.rs index 0214813..fa053f7 100644 --- a/server/crates/arbiter-server/src/actors/vault/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault/mod.rs @@ -25,10 +25,10 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; pub mod events { #[derive(Clone, Copy)] - pub struct VaultBootstrapped; + pub struct Bootstrapped; #[derive(Clone, Copy)] - pub struct VaultUnsealed; + pub struct Unsealed; #[derive(Clone, Copy)] pub struct VaultResealed; @@ -213,7 +213,7 @@ impl Vault { }); info!("Vault bootstrapped successfully"); - self.events.tell(Publish(events::VaultBootstrapped)).await; + self.events.tell(Publish(events::Bootstrapped)).await; Ok(()) } @@ -269,7 +269,7 @@ impl Vault { }); info!("Vault unsealed successfully"); - self.events.tell(Publish(events::VaultUnsealed)).await; + self.events.tell(Publish(events::Unsealed)).await; Ok(()) } diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs index cc93dc6..775b481 100644 --- a/server/crates/arbiter-server/src/grpc/mod.rs +++ b/server/crates/arbiter-server/src/grpc/mod.rs @@ -10,7 +10,6 @@ use tonic::{Request, Response, Status, async_trait}; use tracing::info; use crate::{ - grpc::user_agent::start, peers::{client::ClientConnection, user_agent::UserAgentConnection}, }; @@ -63,7 +62,7 @@ impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Ser let (bi, rx) = GrpcBi::from_bi_stream(req_stream); - tokio::spawn(start( + tokio::spawn(user_agent::start( UserAgentConnection { db: self.context.db.clone(), actors: self.context.actors.clone(), diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index b94e7ab..403163e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -1,4 +1,4 @@ -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; use arbiter_proto::{ proto::user_agent::{ @@ -14,8 +14,12 @@ use tonic::Status; use tracing::{error, info, warn}; use crate::{ + crypto::integrity, grpc::request_tracker::RequestTracker, - peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, + peers::user_agent::{ + Credentials, OutOfBand, UserAgentConnection, UserAgentSession, + vault_gate::VaultGate, + }, }; mod auth; @@ -24,6 +28,7 @@ mod inbound; mod outbound; mod sdk_client; mod vault; +mod vault_gate; pub struct OutOfBandAdapter(mpsc::Sender); @@ -124,27 +129,115 @@ pub async fn start( ) { let mut request_tracker = RequestTracker::default(); - let (id, pubkey) = match auth::start(&mut conn, &mut bi, &mut request_tracker).await { - Ok(pubkey) => pubkey, + let auth_creds = match auth::start(&mut conn, &mut bi, &mut request_tracker).await { + Ok(creds) => creds, Err(e) => { warn!(error = ?e, "Authentication failed"); return; } }; - info!(?pubkey, "User authenticated successfully"); + info!(pubkey = ?auth_creds.creds.pubkey, "User authenticated successfully"); + + let creds = if integrity::is_signing_available(&conn.actors.vault) + .await + .unwrap_or(false) + { + // Vault is unsealed; integrity was verified during auth — promote directly. + auth_creds.creds + } else { + // Vault is sealed/unbootstrapped; run the VaultGate phase. + let (promotion_tx, promotion_rx) = oneshot::channel(); + let gate = VaultGate::spawn(VaultGate::new( + auth_creds, + conn.actors.clone(), + conn.db.clone(), + promotion_tx, + )); + + let result = vault_gate_loop(&mut bi, &gate, &mut request_tracker, promotion_rx).await; + gate.kill(); + + match result { + Some(creds) => creds, + None => return, + } + }; let (oob_sender, oob_receiver) = mpsc::channel(16); let oob_adapter = OutOfBandAdapter(oob_sender); - let actor = UserAgentSession::spawn(UserAgentSession::new( - conn, - id, - pubkey, - Box::new(oob_adapter), - )); + let actor = UserAgentSession::spawn(UserAgentSession::new(conn, creds, Box::new(oob_adapter))); let actor_for_cleanup = actor.clone(); dispatch_loop(bi, actor, oob_receiver, request_tracker).await; actor_for_cleanup.kill(); } + +async fn vault_gate_loop( + bi: &mut GrpcBi, + gate: &ActorRef, + request_tracker: &mut RequestTracker, + mut promotion_rx: oneshot::Receiver>, +) -> Option { + loop { + tokio::select! { + result = &mut promotion_rx => { + return match result { + Ok(Ok(creds)) => Some(creds), + Ok(Err(e)) => { + warn!(error = ?e, "VaultGate promotion failed"); + None + } + Err(_) => { + warn!("VaultGate promotion channel closed unexpectedly"); + None + } + }; + } + + message = bi.recv() => { + let Some(message) = message else { return None; }; + + let conn = match message { + Ok(conn) => conn, + Err(err) => { + warn!(error = ?err, "Failed to receive request during vault gate phase"); + return None; + } + }; + + let request_id = match request_tracker.request(conn.id) { + Ok(id) => id, + Err(err) => { + let _ = bi.send(Err(err)).await; + return None; + } + }; + + let Some(payload) = conn.payload else { + let _ = bi.send(Err(Status::invalid_argument("Missing request payload"))).await; + return None; + }; + + let response = match payload { + UserAgentRequestPayload::Vault(req) => vault_gate::dispatch(gate, req).await, + _ => Err(Status::permission_denied("Only vault operations are permitted before unsealing")), + }; + + match response { + Ok(Some(payload)) => { + if bi.send(Ok(UserAgentResponse { id: Some(request_id), payload: Some(payload) })).await.is_err() { + return None; + } + } + Ok(None) => {} + Err(status) => { + let _ = bi.send(Err(status)).await; + return None; + } + } + } + } + } +} diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs index acb65fc..ab8d70e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs @@ -19,7 +19,7 @@ use tracing::warn; use crate::{ grpc::request_tracker::RequestTracker, - peers::user_agent::{UserAgentConnection, auth}, + peers::user_agent::{AuthCredentials, UserAgentConnection, auth}, }; pub struct AuthTransportAdapter<'a> { @@ -167,7 +167,7 @@ pub async fn start( conn: &mut UserAgentConnection, bi: &mut GrpcBi, request_tracker: &mut RequestTracker, -) -> Result<(i32, authn::PublicKey), auth::Error> { +) -> Result { let transport = AuthTransportAdapter::new(bi, request_tracker); auth::authenticate(conn, transport).await } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs index 386e7c5..2999b72 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/evm.rs @@ -29,7 +29,7 @@ use crate::{ }, peers::user_agent::{ UserAgentSession, - session::connection::{ + session::handlers::{ GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList, HandleSignTransaction, SignTransactionError as SessionSignTransactionError, diff --git a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs index fbf025f..aa3f017 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs @@ -25,7 +25,7 @@ use crate::{ grpc::Convert, peers::user_agent::{ OutOfBand, UserAgentSession, - session::connection::{ + session::handlers::{ HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove, HandleRevokeEvmWalletAccess, HandleSdkClientList, }, diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault.rs index f645c2f..4d123ae 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault.rs @@ -1,34 +1,15 @@ use arbiter_proto::proto::shared::VaultState as ProtoVaultState; use arbiter_proto::proto::user_agent::{ user_agent_response::Payload as UserAgentResponsePayload, - vault::{ - self as proto_vault, - bootstrap::{ - self as proto_bootstrap, BootstrapEncryptedKey as ProtoBootstrapEncryptedKey, - BootstrapResult as ProtoBootstrapResult, - }, - request::Payload as VaultRequestPayload, - response::Payload as VaultResponsePayload, - unseal::{ - self as proto_unseal, UnsealEncryptedKey as ProtoUnsealEncryptedKey, - UnsealResult as ProtoUnsealResult, UnsealStart, - request::Payload as UnsealRequestPayload, response::Payload as UnsealResponsePayload, - }, - }, + vault::{self as proto_vault, request::Payload as VaultRequestPayload, response::Payload as VaultResponsePayload}, }; -use kameo::{actor::ActorRef, error::SendError}; +use kameo::actor::ActorRef; use tonic::Status; use tracing::warn; use crate::{ actors::vault::VaultState, - peers::user_agent::{ - UserAgentSession, - session::connection::{ - BootstrapError, HandleBootstrapEncryptedKey, HandleQueryVaultState, - HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError, - }, - }, + peers::user_agent::{UserAgentSession, session::handlers::HandleQueryVaultState}, }; fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { @@ -37,18 +18,6 @@ fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayloa }) } -fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload { - wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response { - payload: Some(payload), - })) -} - -fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload { - wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response { - result: result.into(), - })) -} - pub(super) async fn dispatch( actor: &ActorRef, req: proto_vault::Request, @@ -59,109 +28,14 @@ pub(super) async fn dispatch( match payload { VaultRequestPayload::QueryState(_) => handle_query_vault_state(actor).await, - VaultRequestPayload::Unseal(req) => dispatch_unseal_request(actor, req).await, - VaultRequestPayload::Bootstrap(req) => handle_bootstrap_request(actor, req).await, + VaultRequestPayload::Unseal(_) | VaultRequestPayload::Bootstrap(_) => { + Err(Status::permission_denied( + "Vault is already unsealed; unseal/bootstrap not permitted in session", + )) + } } } -async fn dispatch_unseal_request( - actor: &ActorRef, - req: proto_unseal::Request, -) -> Result, Status> { - let Some(payload) = req.payload else { - return Err(Status::invalid_argument("Missing unseal request payload")); - }; - - match payload { - UnsealRequestPayload::Start(req) => handle_unseal_start(actor, req).await, - UnsealRequestPayload::EncryptedKey(req) => handle_unseal_encrypted_key(actor, req).await, - } -} - -async fn handle_unseal_start( - actor: &ActorRef, - req: UnsealStart, -) -> Result, Status> { - let client_pubkey = <[u8; 32]>::try_from(req.client_pubkey) - .map(x25519_dalek::PublicKey::from) - .map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?; - - let response = actor - .ask(HandleUnsealRequest { client_pubkey }) - .await - .map_err(|err| { - warn!(error = ?err, "Failed to handle unseal start request"); - Status::internal("Failed to start unseal flow") - })?; - - Ok(Some(wrap_unseal_response(UnsealResponsePayload::Start( - proto_unseal::UnsealStartResponse { - server_pubkey: response.server_pubkey.as_bytes().to_vec(), - }, - )))) -} - -async fn handle_unseal_encrypted_key( - actor: &ActorRef, - req: ProtoUnsealEncryptedKey, -) -> Result, Status> { - let result = match actor - .ask(HandleUnsealEncryptedKey { - nonce: req.nonce, - ciphertext: req.ciphertext, - associated_data: req.associated_data, - }) - .await - { - Ok(()) => ProtoUnsealResult::Success, - Err(SendError::HandlerError(UnsealError::InvalidKey)) => ProtoUnsealResult::InvalidKey, - Err(err) => { - warn!(error = ?err, "Failed to handle unseal request"); - return Err(Status::internal("Failed to unseal vault")); - } - }; - Ok(Some(wrap_unseal_response(UnsealResponsePayload::Result( - result.into(), - )))) -} - -async fn handle_bootstrap_request( - actor: &ActorRef, - req: proto_bootstrap::Request, -) -> Result, Status> { - let encrypted_key = req - .encrypted_key - .ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))?; - handle_bootstrap_encrypted_key(actor, encrypted_key).await -} - -async fn handle_bootstrap_encrypted_key( - actor: &ActorRef, - req: ProtoBootstrapEncryptedKey, -) -> Result, Status> { - let result = match actor - .ask(HandleBootstrapEncryptedKey { - nonce: req.nonce, - ciphertext: req.ciphertext, - associated_data: req.associated_data, - }) - .await - { - Ok(()) => ProtoBootstrapResult::Success, - Err(SendError::HandlerError(BootstrapError::InvalidKey)) => { - ProtoBootstrapResult::InvalidKey - } - Err(SendError::HandlerError(BootstrapError::AlreadyBootstrapped)) => { - ProtoBootstrapResult::AlreadyBootstrapped - } - Err(err) => { - warn!(error = ?err, "Failed to handle bootstrap request"); - return Err(Status::internal("Failed to bootstrap vault")); - } - }; - Ok(Some(wrap_bootstrap_response(result))) -} - async fn handle_query_vault_state( actor: &ActorRef, ) -> Result, Status> { diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs new file mode 100644 index 0000000..2436064 --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs @@ -0,0 +1,151 @@ +use arbiter_proto::proto::user_agent::{ + user_agent_response::Payload as UserAgentResponsePayload, + vault::{ + self as proto_vault, + bootstrap::{ + self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult, + }, + request::Payload as VaultRequestPayload, + response::Payload as VaultResponsePayload, + unseal::{ + self as proto_unseal, UnsealResult as ProtoUnsealResult, UnsealStart, + request::Payload as UnsealRequestPayload, response::Payload as UnsealResponsePayload, + }, + }, +}; +use kameo::{actor::ActorRef, error::SendError}; +use tonic::Status; +use tracing::warn; + +use crate::peers::user_agent::vault_gate::{ + self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, + VaultGate, +}; + +fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { + UserAgentResponsePayload::Vault(proto_vault::Response { + payload: Some(payload), + }) +} + +fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload { + wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response { + payload: Some(payload), + })) +} + +fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload { + wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response { + result: result.into(), + })) +} + +pub(super) async fn dispatch( + gate: &ActorRef, + req: proto_vault::Request, +) -> Result, Status> { + let Some(payload) = req.payload else { + return Err(Status::invalid_argument("Missing vault request payload")); + }; + + match payload { + VaultRequestPayload::QueryState(_) => { + use arbiter_proto::proto::shared::VaultState as ProtoVaultState; + Ok(Some(wrap_vault_response(VaultResponsePayload::State( + ProtoVaultState::Sealed.into(), + )))) + } + VaultRequestPayload::Unseal(req) => dispatch_unseal(gate, req).await, + VaultRequestPayload::Bootstrap(req) => dispatch_bootstrap(gate, req).await, + } +} + +async fn dispatch_unseal( + gate: &ActorRef, + req: proto_unseal::Request, +) -> Result, Status> { + let Some(payload) = req.payload else { + return Err(Status::invalid_argument("Missing unseal request payload")); + }; + + match payload { + UnsealRequestPayload::Start(req) => handle_unseal_start(gate, req).await, + UnsealRequestPayload::EncryptedKey(req) => handle_unseal_encrypted_key(gate, req).await, + } +} + +async fn handle_unseal_start( + gate: &ActorRef, + req: UnsealStart, +) -> Result, Status> { + let client_pubkey = <[u8; 32]>::try_from(req.client_pubkey) + .map(x25519_dalek::PublicKey::from) + .map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?; + + let response = gate + .ask(HandleHandshake { client_pubkey }) + .await + .map_err(|err| { + warn!(error = ?err, "Failed to handle unseal start"); + Status::internal("Failed to start unseal flow") + })?; + + Ok(Some(wrap_unseal_response(UnsealResponsePayload::Start( + proto_unseal::UnsealStartResponse { + server_pubkey: response.server_pubkey.as_bytes().to_vec(), + }, + )))) +} + +async fn handle_unseal_encrypted_key( + gate: &ActorRef, + req: arbiter_proto::proto::user_agent::vault::unseal::UnsealEncryptedKey, +) -> Result, Status> { + let result = match gate + .ask(HandleUnsealEncryptedKey { + nonce: req.nonce, + ciphertext: req.ciphertext, + associated_data: req.associated_data, + }) + .await + { + Ok(()) => ProtoUnsealResult::Success, + Err(SendError::HandlerError(vault_gate::Error::InvalidKey)) => ProtoUnsealResult::InvalidKey, + Err(err) => { + warn!(error = ?err, "Failed to handle unseal request"); + return Err(Status::internal("Failed to unseal vault")); + } + }; + Ok(Some(wrap_unseal_response(UnsealResponsePayload::Result( + result.into(), + )))) +} + +async fn dispatch_bootstrap( + gate: &ActorRef, + req: proto_bootstrap::Request, +) -> Result, Status> { + let encrypted_key = req + .encrypted_key + .ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))?; + + let result = match gate + .ask(HandleBootstrapEncryptedKey { + nonce: encrypted_key.nonce, + ciphertext: encrypted_key.ciphertext, + associated_data: encrypted_key.associated_data, + }) + .await + { + Ok(()) => ProtoBootstrapResult::Success, + Err(SendError::HandlerError(vault_gate::Error::InvalidKey)) => ProtoBootstrapResult::InvalidKey, + Err(SendError::HandlerError(vault_gate::Error::AlreadyBootstrapped)) => { + ProtoBootstrapResult::AlreadyBootstrapped + } + Err(err) => { + warn!(error = ?err, "Failed to handle bootstrap request"); + return Err(Status::internal("Failed to bootstrap vault")); + } + }; + Ok(Some(wrap_bootstrap_response(result))) +} diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs similarity index 94% rename from server/crates/arbiter-server/src/peers/user_agent/auth.rs rename to server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs index dbf1740..a30bc5e 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs @@ -5,7 +5,7 @@ use tracing::error; mod state; use state::*; -use super::UserAgentConnection; +use super::{AuthCredentials, UserAgentConnection}; #[derive(Debug, Clone)] pub enum Inbound { @@ -69,7 +69,7 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { pub async fn authenticate( props: &mut UserAgentConnection, transport: T, -) -> Result<(i32, authn::PublicKey), Error> +) -> Result where T: Bi> + Send, { @@ -82,7 +82,7 @@ where }; match state.process_event(parse_auth_event(payload)).await { - Ok(AuthStates::AuthOk(result)) => return Ok((result.id, result.pubkey.clone())), + Ok(AuthStates::AuthOk(result)) => return Ok(result.clone()), Err(AuthError::ActionFailed(err)) => { error!(?err, "State machine action failed"); return Err(err); diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index 91a7b86..7f9dbb5 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -1,7 +1,7 @@ -use super::super::{UserAgentConnection, UserAgentCredentials}; +use super::super::{AuthCredentials, Credentials, UserAgentConnection}; use arbiter_crypto::authn::{self, USERAGENT_CONTEXT}; use arbiter_proto::transport::Bi; -use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update}; +use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, sqlite::Sqlite, update}; use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::actor::ActorRef; use tracing::error; @@ -33,21 +33,18 @@ pub struct ChallengeSolution { pub solution: Vec, } -pub struct AuthOk { - pub id: i32, - pub pubkey: authn::PublicKey, -} - smlang::statemachine!( name: Auth, custom_error: true, transitions: { *Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext), - Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthOk), - SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthOk), + Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthCredentials), + SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthCredentials), } ); +const NONCE_START: i32 = 1; + /// Returns the current nonce, ready to use for the challenge nonce. async fn get_current_nonce_and_id( db: &DatabasePool, @@ -94,9 +91,12 @@ async fn verify_integrity( let _result = integrity::verify_entity( &mut db_conn, vault, - &UserAgentCredentials { - pubkey: pubkey.clone(), - nonce, + &AuthCredentials { + creds: Credentials { + id, + pubkey: pubkey.clone(), + }, + new_nonce: nonce, }, id, ) @@ -109,49 +109,46 @@ async fn verify_integrity( Ok(()) } -async fn create_nonce( - db: &DatabasePool, - vault: &ActorRef, +async fn compute_current_nonce( + conn: &mut impl AsyncConnection, pubkey: &authn::PublicKey, ) -> Result<(i32, i32), Error> { - let mut db_conn = db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::internal("Database unavailable") - })?; - let (id, new_nonce) = db_conn - .exclusive_transaction(|conn| { - Box::pin(async move { - let (id, new_nonce): (i32, i32) = update(useragent_client::table) - .filter(useragent_client::public_key.eq(pubkey.to_bytes())) - .set(useragent_client::nonce.eq(useragent_client::nonce + 1)) - .returning((useragent_client::id, useragent_client::nonce)) - .get_result(conn) - .await - .map_err(|e| { - error!(error = ?e, "Database error"); - Error::internal("Database operation failed") - })?; - - integrity::sign_entity( - conn, - vault, - &UserAgentCredentials { - pubkey: pubkey.clone(), - nonce: new_nonce, - }, - id, - ) - .await - .map_err(|e| { - error!(?e, "Integrity signature update failed"); - Error::internal("Database error") - })?; - - Result::<_, Error>::Ok((id, new_nonce)) - }) + update(useragent_client::table) + .filter(useragent_client::public_key.eq(pubkey.to_bytes())) + .set(useragent_client::nonce.eq(useragent_client::nonce + 1)) + .returning((useragent_client::id, useragent_client::nonce)) + .get_result(conn) + .await + .map_err(|e| { + error!(error = ?e, "Database error incrementing nonce"); + Error::internal("Database operation failed") }) - .await?; - Ok((id, new_nonce)) +} + +async fn resign_credentials( + conn: &mut impl AsyncConnection, + vault: &ActorRef, + id: i32, + pubkey: &authn::PublicKey, + new_nonce: i32, +) -> Result<(), Error> { + integrity::sign_entity( + conn, + vault, + &AuthCredentials { + creds: Credentials { + id, + pubkey: pubkey.clone(), + }, + new_nonce, + }, + id, + ) + .await + .map_err(|e| { + error!(?e, "Integrity signature update failed"); + Error::internal("Database error") + }) } async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result { @@ -161,8 +158,6 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result Result { - verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + let is_signing = integrity::is_signing_available(&self.conn.actors.vault) + .await + .unwrap_or(false); - let (id, nonce) = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + if is_signing { + verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + } + + let vault = self.conn.actors.vault.clone(); + let mut conn = self.conn.db.get().await.map_err(|e| { + error!(error = ?e, "Database pool error"); + Error::internal("Database unavailable") + })?; + + let (id, nonce) = conn + .exclusive_transaction(|conn| { + let pubkey = pubkey.clone(); + let vault = vault.clone(); + Box::pin(async move { + let (id, new_nonce) = compute_current_nonce(conn, &pubkey).await?; + if is_signing { + resign_credentials(conn, &vault, id, &pubkey, new_nonce).await?; + } + Result::<_, Error>::Ok((id, new_nonce)) + }) + }) + .await?; self.transport .send(Ok(Outbound::AuthChallenge { nonce })) @@ -224,7 +243,7 @@ where async fn verify_bootstrap_token( &mut self, BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest, - ) -> Result { + ) -> Result { let token_ok: bool = self .conn .actors @@ -245,12 +264,15 @@ where match token_ok { true => { - let id = register_key(&self.conn.db, &pubkey).await?; + let id = register_key(&self.conn.db, &pubkey).await?; self.transport .send(Ok(Outbound::AuthSuccess)) .await .map_err(|_| Error::Transport)?; - Ok(AuthOk { id, pubkey }) + Ok(AuthCredentials { + creds: Credentials { id, pubkey }, + new_nonce: NONCE_START, + }) } false => { error!("Invalid bootstrap token provided"); @@ -273,7 +295,7 @@ where key, }: &ChallengeContext, ChallengeSolution { solution }: ChallengeSolution, - ) -> Result { + ) -> Result { let signature = authn::Signature::try_from(solution.as_slice()).map_err(|_| { error!("Failed to decode signature in challenge solution"); Error::InvalidChallengeSolution @@ -287,7 +309,13 @@ where .send(Ok(Outbound::AuthSuccess)) .await .map_err(|_| Error::Transport)?; - Ok(AuthOk { id: *id, pubkey: key.clone() }) + Ok(AuthCredentials { + creds: Credentials { + id: *id, + pubkey: key.clone(), + }, + new_nonce: *challenge_nonce, + }) } false => { self.transport diff --git a/server/crates/arbiter-server/src/peers/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/mod.rs index 4168732..5601101 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/mod.rs @@ -3,13 +3,45 @@ use crate::{ }; use arbiter_crypto::authn; -#[derive(Debug)] -pub struct UserAgentCredentials { +pub mod auth; +pub mod session; +pub mod vault_gate; + + +#[derive(Debug, Clone, Hash)] +pub struct Credentials { + pub id: i32, pub pubkey: authn::PublicKey, - pub nonce: i32, +} +impl Hashable for Credentials { + fn hash(&self, hasher: &mut H) { + self.id.hash(hasher); + self.pubkey.hash(hasher); + } } -impl Integrable for UserAgentCredentials { +#[derive(Debug, Clone)] +pub struct AuthCredentials { + pub creds: Credentials, + // denotes new nonce, not current + pub new_nonce: i32, +} + +impl Hashable for authn::PublicKey { + fn hash(&self, hasher: &mut H) { + hasher.update(self.to_bytes()); + } +} + +impl Hashable for AuthCredentials { + fn hash(&self, hasher: &mut H) { + self.creds.hash(hasher); + self.new_nonce.hash(hasher); + } +} + + +impl Integrable for AuthCredentials { const KIND: &'static str = "useragent_credentials"; } @@ -31,23 +63,9 @@ impl UserAgentConnection { } } -pub mod auth; -pub mod session; + pub use auth::authenticate; pub use session::UserAgentSession; -use crate::crypto::integrity::hashing::Hashable; - -impl Hashable for authn::PublicKey { - fn hash(&self, hasher: &mut H) { - hasher.update(self.to_bytes()); - } -} - -impl Hashable for UserAgentCredentials { - fn hash(&self, hasher: &mut H) { - self.pubkey.hash(hasher); - self.nonce.hash(hasher); - } -} +use crate::crypto::integrity::hashing::Hashable; \ No newline at end of file diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs b/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs similarity index 54% rename from server/crates/arbiter-server/src/peers/user_agent/session/connection.rs rename to server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs index b8e9d2a..53b65e0 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs @@ -28,88 +28,10 @@ use crate::db::models::{ use crate::evm::policies::{Grant, SpecificGrant}; use crate::{ actors::vault::VaultState, - peers::user_agent::session::state::{UnsealContext, UserAgentEvents}, }; -use super::{Error, UserAgentSession, state}; +use super::{Error, UserAgentSession}; -impl UserAgentSession { - fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> { - let state::UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { - error!("Received encrypted key in invalid state"); - return Err(Error::internal("Invalid state for unseal encrypted key")); - }; - - let ephemeral_secret = { - #[allow( - clippy::unwrap_used, - reason = "Mutex poison is unrecoverable and should panic" - )] - let mut secret_lock = unseal_context.secret.lock().unwrap(); - let secret = secret_lock.take(); - match secret { - Some(secret) => secret, - None => { - drop(secret_lock); - error!("Ephemeral secret already taken"); - return Err(Error::internal("Ephemeral secret already taken")); - } - } - }; - - Ok((ephemeral_secret, unseal_context.client_public_key)) - } - - fn decrypt_client_key_material( - ephemeral_secret: EphemeralSecret, - client_public_key: PublicKey, - nonce: &[u8], - ciphertext: &[u8], - associated_data: &[u8], - ) -> Result>, ()> { - let nonce = XNonce::from_slice(nonce); - - let shared_secret = ephemeral_secret.diffie_hellman(&client_public_key); - let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into()); - - let mut key_buffer = SafeCell::new(ciphertext.to_vec()); - - let decryption_result = key_buffer.write_inline(|write_handle| { - cipher.decrypt_in_place(nonce, associated_data, write_handle) - }); - - match decryption_result { - Ok(_) => Ok(key_buffer), - Err(err) => { - error!(?err, "Failed to decrypt encrypted key material"); - Err(()) - } - } - } -} - -pub struct UnsealStartResponse { - pub server_pubkey: PublicKey, -} - -#[derive(Debug, Error)] -pub enum UnsealError { - #[error("Invalid key provided for unsealing")] - InvalidKey, - #[error("Internal error during unsealing process")] - General(#[from] super::Error), -} - -#[derive(Debug, Error)] -pub enum BootstrapError { - #[error("Invalid key provided for bootstrapping")] - InvalidKey, - #[error("Vault is already bootstrapped")] - AlreadyBootstrapped, - - #[error("Internal error during bootstrapping process")] - General(#[from] super::Error), -} #[derive(Debug, Error)] pub enum SignTransactionError { @@ -129,153 +51,6 @@ pub enum GrantMutationError { Internal, } -#[messages] -impl UserAgentSession { - #[message] - pub async fn handle_unseal_request( - &mut self, - client_pubkey: x25519_dalek::PublicKey, - ) -> Result { - let secret = EphemeralSecret::random(); - let public_key = PublicKey::from(&secret); - - self.transition(UserAgentEvents::UnsealRequest(UnsealContext { - secret: Mutex::new(Some(secret)), - client_public_key: client_pubkey, - }))?; - - Ok(UnsealStartResponse { - server_pubkey: public_key, - }) - } - - #[message] - pub async fn handle_unseal_encrypted_key( - &mut self, - nonce: Vec, - ciphertext: Vec, - associated_data: Vec, - ) -> Result<(), UnsealError> { - let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { - Ok(values) => values, - Err(Error::State) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Err(UnsealError::InvalidKey); - } - Err(_err) => { - return Err(Error::internal("Failed to take unseal secret").into()); - } - }; - - let seal_key_buffer = match Self::decrypt_client_key_material( - ephemeral_secret, - client_public_key, - &nonce, - &ciphertext, - &associated_data, - ) { - Ok(buffer) => buffer, - Err(()) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Err(UnsealError::InvalidKey); - } - }; - - match self - .props - .actors - .vault - .ask(TryUnseal { - seal_key_raw: seal_key_buffer, - }) - .await - { - Ok(_) => { - info!("Successfully unsealed key with client-provided key"); - self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(()) - } - Err(SendError::HandlerError(vault::Error::InvalidKey)) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(UnsealError::InvalidKey) - } - Err(SendError::HandlerError(err)) => { - error!(?err, "Vault failed to unseal key"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(UnsealError::InvalidKey) - } - Err(err) => { - error!(?err, "Failed to send unseal request to vault"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(Error::internal("Vault actor error").into()) - } - } - } - - #[message] - pub(crate) async fn handle_bootstrap_encrypted_key( - &mut self, - nonce: Vec, - ciphertext: Vec, - associated_data: Vec, - ) -> Result<(), BootstrapError> { - let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() { - Ok(values) => values, - Err(Error::State) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Err(BootstrapError::InvalidKey); - } - Err(err) => return Err(err.into()), - }; - - let seal_key_buffer = match Self::decrypt_client_key_material( - ephemeral_secret, - client_public_key, - &nonce, - &ciphertext, - &associated_data, - ) { - Ok(buffer) => buffer, - Err(()) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - return Err(BootstrapError::InvalidKey); - } - }; - - match self - .props - .actors - .vault - .ask(Bootstrap { - seal_key_raw: seal_key_buffer, - }) - .await - { - Ok(_) => { - info!("Successfully bootstrapped vault with client-provided key"); - self.transition(UserAgentEvents::ReceivedValidKey)?; - Ok(()) - } - Err(SendError::HandlerError(vault::Error::AlreadyBootstrapped)) => { - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(BootstrapError::AlreadyBootstrapped) - } - Err(SendError::HandlerError(err)) => { - error!(?err, "Vault failed to bootstrap vault"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(BootstrapError::InvalidKey) - } - Err(err) => { - error!(?err, "Failed to send bootstrap request to vault"); - self.transition(UserAgentEvents::ReceivedInvalidKey)?; - Err(BootstrapError::General(Error::internal( - "Vault actor error", - ))) - } - } - } -} - #[messages] impl UserAgentSession { #[message] diff --git a/server/crates/arbiter-server/src/peers/user_agent/session.rs b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs similarity index 59% rename from server/crates/arbiter-server/src/peers/user_agent/session.rs rename to server/crates/arbiter-server/src/peers/user_agent/session/mod.rs index 8e94931..55cd9e5 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs @@ -1,12 +1,11 @@ use arbiter_crypto::authn; use diesel::{ExpressionMethods, QueryDsl}; -use diesel_async::{AsyncConnection, RunQueryDsl}; +use diesel_async::{RunQueryDsl}; use kameo_actors::message_bus::Register; use std::{borrow::Cow, collections::HashMap}; use arbiter_proto::transport::Sender; -use async_trait::async_trait; use kameo::{Actor, actor::ActorRef, messages, prelude::Message}; use thiserror::Error; use tracing::error; @@ -15,10 +14,8 @@ use crate::{ actors::{ flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, vault::events, - }, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::UserAgentCredentials} + }, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::{AuthCredentials, Credentials}} }; -mod state; -use state::{DummyContext, UserAgentEvents, UserAgentStateMachine}; use super::{OutOfBand, UserAgentConnection}; @@ -58,41 +55,28 @@ pub struct PendingClientApproval { } pub struct UserAgentSession { - id: i32, - pubkey: authn::PublicKey, + creds: Credentials, props: UserAgentConnection, - state: UserAgentStateMachine, sender: Box>, pending_client_approvals: HashMap, PendingClientApproval>, } -pub mod connection; +pub mod handlers; impl UserAgentSession { pub(crate) fn new( props: UserAgentConnection, - id: i32, - pubkey: authn::PublicKey, + creds: Credentials, sender: Box>, ) -> Self { Self { - id, + creds, props, - pubkey, - state: UserAgentStateMachine::new(DummyContext), sender, pending_client_approvals: Default::default(), } } - - fn transition(&mut self, event: UserAgentEvents) -> Result<(), Error> { - self.state.process_event(event).map_err(|e| { - error!(?e, "State transition failed"); - Error::State - })?; - Ok(()) - } } #[messages] @@ -128,61 +112,6 @@ impl UserAgentSession { } } -impl Message for UserAgentSession { - type Reply = Result<(), Error>; - - async fn handle( - &mut self, - _: events::VaultBootstrapped, - ctx: &mut kameo::prelude::Context, - ) -> Self::Reply { - let Ok(mut conn) = self.props.db.get().await else { - error!("Failed to get database connection for vault bootstrapped event"); - ctx.stop(); - return Err(Error::internal("Failed to get database connection")); - }; - - - let result = conn.exclusive_transaction(|conn| { - Box::pin(async { - let nonce: i32 = useragent_client::table - .filter(useragent_client::id.eq(self.id)) - .select(useragent_client::nonce) - .first::(conn) - .await - .map_err(|e| { - error!(?e, "Failed to get nonce for useragent bootstrapping"); - Error::internal("Failed to sign user agent credentials") - })?; - - let entity = UserAgentCredentials { - pubkey: self.pubkey.clone(), - nonce, - }; - - integrity::sign_entity(conn, &self.props.actors.vault, &entity, self.id) - .await - .map_err(|e| { - error!(?e, "Failed to sign user agent credentials during vault bootstrapping"); - Error::internal("Failed to sign user agent credentials") - })?; - - Result::<_, Error>::Ok(()) - }) - }).await; - - match result { - Ok(_) => Ok(()), - Err(err) => { - error!(?err, "Error during vault bootstrapping"); - ctx.stop(); - Err(err) - }, - } - - } -} - impl Actor for UserAgentSession { type Args = Self; @@ -192,21 +121,6 @@ impl Actor for UserAgentSession { args: Self::Args, this: kameo::prelude::ActorRef, ) -> Result { - args.props - .actors - .events - .tell(Register( - this.clone().recipient::(), - )) - .await - .map_err(|err| { - error!( - ?err, - "Failed to register user agent connection with event bus" - ); - Error::internal("Failed to register user agent connection with event bus") - })?; - args.props .actors .flow_coordinator diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/state.rs b/server/crates/arbiter-server/src/peers/user_agent/session/state.rs deleted file mode 100644 index 23ab674..0000000 --- a/server/crates/arbiter-server/src/peers/user_agent/session/state.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::sync::Mutex; - -use x25519_dalek::{EphemeralSecret, PublicKey}; - -pub struct UnsealContext { - pub client_public_key: PublicKey, - pub secret: Mutex>, -} - -smlang::statemachine!( - name: UserAgent, - custom_error: false, - transitions: { - *Idle + UnsealRequest(UnsealContext) / generate_temp_keypair = WaitingForUnsealKey(UnsealContext), - WaitingForUnsealKey(UnsealContext) + ReceivedValidKey = Unsealed, - WaitingForUnsealKey(UnsealContext) + ReceivedInvalidKey = Idle, - } -); - -pub struct DummyContext; -impl UserAgentStateMachineContext for DummyContext { - #[allow(missing_docs)] - #[allow(clippy::unused_unit)] - fn generate_temp_keypair(&mut self, event_data: UnsealContext) -> Result { - Ok(event_data) - } -} diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs new file mode 100644 index 0000000..806bd7e --- /dev/null +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs @@ -0,0 +1,295 @@ +use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; +use chacha20poly1305::{AeadInPlace, KeyInit as _, XChaCha20Poly1305, XNonce}; +use kameo::{Actor, error::SendError, messages, prelude::Message}; +use kameo_actors::message_bus::Register; +use tokio::sync::oneshot; +use tracing::{error, info}; +use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; + +pub mod state; +use state::*; + +use super::{AuthCredentials, Credentials}; +use crate::{ + actors::{ + GlobalActors, + vault::{self, Bootstrap, TryUnseal, events}, + }, + crypto::integrity::{self, AttestationStatus}, + db::DatabasePool, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Vault is already bootstrapped")] + AlreadyBootstrapped, + #[error("Invalid key provided")] + InvalidKey, + + #[error("State transition failed")] + State, + + #[error("Internal error: {0}")] + Internal(String), +} +impl Error { + fn internal(message: impl Into) -> Self { + Self::Internal(message.into()) + } +} + +pub struct HandshakeResponse { + pub server_pubkey: PublicKey, +} + +pub struct VaultGate { + pub auth_creds: AuthCredentials, + pub promotion_tx: Option>>, + pub state: State, + pub actors: GlobalActors, + pub db: DatabasePool, +} + +impl VaultGate { + pub fn new( + auth_creds: AuthCredentials, + actors: GlobalActors, + db: DatabasePool, + promotion_tx: oneshot::Sender>, + ) -> Self { + Self { + auth_creds, + state: State::default(), + actors, + db, + promotion_tx: Some(promotion_tx), + } + } +} + +impl Actor for VaultGate { + type Args = Self; + + type Error = (); + + async fn on_start( + args: Self::Args, + actor_ref: kameo::prelude::ActorRef, + ) -> Result { + let _ = args + .actors + .events + .tell(Register( + actor_ref.clone().recipient::(), + )) + .await; + let _ = args + .actors + .events + .tell(Register(actor_ref.recipient::())) + .await; + Ok(args) + } +} + +impl VaultGate { + fn decrypt_key( + secret: &SharedSecret, + nonce: &[u8], + ciphertext: &[u8], + associated_data: &[u8], + ) -> Result>, ()> { + let nonce = XNonce::from_slice(nonce); + + let cipher = XChaCha20Poly1305::new(secret.as_bytes().into()); + + let mut key_buffer = SafeCell::new(ciphertext.to_vec()); + + let decryption_result = key_buffer.write_inline(|write_handle| { + cipher.decrypt_in_place(nonce, associated_data, write_handle) + }); + + match decryption_result { + Ok(_) => Ok(key_buffer), + Err(err) => { + error!(?err, "Failed to decrypt encrypted key material"); + Err(()) + } + } + } +} + +#[messages] +impl VaultGate { + #[message] + pub async fn handle_handshake( + &mut self, + client_pubkey: x25519_dalek::PublicKey, + ) -> Result { + let ephemeral_secret = EphemeralSecret::random(); + let public_key = PublicKey::from(&ephemeral_secret); + + let secret = ephemeral_secret.diffie_hellman(&client_pubkey); + + self.state = State::ReadyForExchange { + server_key: public_key.clone(), + secret, + }; + + Ok(HandshakeResponse { + server_pubkey: public_key, + }) + } + + #[message] + pub async fn handle_unseal_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Result<(), Error> { + let State::ReadyForExchange { secret, .. } = &self.state else { + return Err(Error::State); + }; + + let seal_key_buffer = match Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) + { + Ok(buffer) => buffer, + Err(()) => { + return Err(Error::InvalidKey); + } + }; + + match self + .actors + .vault + .ask(TryUnseal { + seal_key_raw: seal_key_buffer, + }) + .await + { + Ok(_) => { + info!("Successfully unsealed key with client-provided key"); + Ok(()) + } + Err(SendError::HandlerError(vault::Error::InvalidKey)) => Err(Error::InvalidKey), + Err(SendError::HandlerError(err)) => { + error!(?err, "Vault failed to unseal key"); + Err(Error::InvalidKey) + } + Err(err) => { + error!(?err, "Failed to send unseal request to vault"); + Err(Error::internal("Vault actor error").into()) + } + } + } + + #[message] + pub(crate) async fn handle_bootstrap_encrypted_key( + &mut self, + nonce: Vec, + ciphertext: Vec, + associated_data: Vec, + ) -> Result<(), Error> { + let State::ReadyForExchange { secret, .. } = &self.state else { + return Err(Error::State); + }; + + let seal_key_buffer = match Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) + { + Ok(buffer) => buffer, + Err(()) => { + return Err(Error::InvalidKey); + } + }; + + match self + .actors + .vault + .ask(Bootstrap { + seal_key_raw: seal_key_buffer, + }) + .await + { + Ok(_) => { + info!("Successfully bootstrapped vault with client-provided key"); + Ok(()) + } + Err(SendError::HandlerError(vault::Error::AlreadyBootstrapped)) => { + Err(Error::AlreadyBootstrapped) + } + Err(SendError::HandlerError(err)) => { + error!(?err, "Vault failed to bootstrap vault"); + Err(Error::InvalidKey) + } + Err(err) => { + error!(?err, "Failed to send bootstrap request to vault"); + Err(Error::internal("Vault error")) + } + } + } +} + +impl Message for VaultGate { + type Reply = (); + + async fn handle( + &mut self, + _: events::Bootstrapped, + ctx: &mut kameo::prelude::Context, + ) -> Self::Reply { + let result = async { + let mut conn = self.db.get().await.map_err(|_| Error::internal("DB unavailable"))?; + integrity::sign_entity(&mut conn, &self.actors.vault, &self.auth_creds, self.auth_creds.creds.id) + .await + .map_err(|e| { + error!(?e, "Failed to sign integrity envelope on bootstrap"); + Error::internal("Integrity sign failed") + })?; + Ok(self.auth_creds.creds.clone()) + } + .await; + + if let Some(tx) = self.promotion_tx.take() { + let _ = tx.send(result); + } + ctx.stop(); + } +} + +impl Message for VaultGate { + type Reply = (); + + async fn handle( + &mut self, + _: events::Unsealed, + ctx: &mut kameo::prelude::Context, + ) -> Self::Reply { + let result = async { + let mut conn = self.db.get().await.map_err(|_| Error::internal("DB unavailable"))?; + match integrity::verify_entity( + &mut conn, + &self.actors.vault, + &self.auth_creds, + self.auth_creds.creds.id, + ) + .await + { + Ok(AttestationStatus::Attested) => Ok(self.auth_creds.creds.clone()), + Ok(AttestationStatus::Unavailable) => { + Err(Error::internal("Vault sealed during promotion")) + } + Err(e) => { + error!(?e, "Integrity verification failed during unseal promotion"); + Err(Error::InvalidKey) + } + } + } + .await; + + if let Some(tx) = self.promotion_tx.take() { + let _ = tx.send(result); + } + ctx.stop(); + } +} diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs new file mode 100644 index 0000000..8a86c99 --- /dev/null +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs @@ -0,0 +1,18 @@ +use std::sync::Mutex; + +use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; + + + +pub struct Handshake { + client_pubkey: PublicKey, +} + + + +#[derive(Default)] +pub enum State { + #[default] + Idle, + ReadyForExchange { server_key: PublicKey, secret: SharedSecret }, +} \ No newline at end of file diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index c0665f7..fb3a446 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -8,7 +8,7 @@ use arbiter_server::{ actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, crypto::integrity, db::{self, schema}, - peers::user_agent::{UserAgentConnection, UserAgentCredentials, auth}, + peers::user_agent::{AuthCredentials, Credentials, UserAgentConnection, auth}, }; use diesel::{ExpressionMethods as _, QueryDsl, insert_into}; use diesel_async::RunQueryDsl; @@ -144,9 +144,12 @@ pub async fn test_challenge_auth() { integrity::sign_entity( &mut conn, &actors.vault, - &UserAgentCredentials { - pubkey: new_key.verifying_key().into(), - nonce: 1, + &AuthCredentials { + creds: Credentials { + id, + pubkey: new_key.verifying_key().into(), + }, + new_nonce: 1, }, id, ) @@ -282,9 +285,12 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { integrity::sign_entity( &mut conn, &actors.vault, - &UserAgentCredentials { - pubkey: new_key.verifying_key().into(), - nonce: 1, + &AuthCredentials { + creds: Credentials { + id, + pubkey: new_key.verifying_key().into(), + }, + new_nonce: 1, }, id, ) diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index ba2388b..b63de98 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -1,4 +1,7 @@ -use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; +use arbiter_crypto::{ + authn, + safecell::{SafeCell, SafeCellHandle as _}, +}; use arbiter_server::{ actors::{ GlobalActors, @@ -6,18 +9,19 @@ use arbiter_server::{ }, db, peers::user_agent::{ - UserAgentSession, - session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, + AuthCredentials, Credentials, + vault_gate::{Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate}, }, }; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use kameo::actor::Spawn as _; +use tokio::sync::oneshot; use x25519_dalek::{EphemeralSecret, PublicKey}; -async fn setup_sealed_user_agent( +async fn setup_sealed_gate( seal_key: &[u8], -) -> (db::DatabasePool, kameo::actor::ActorRef) { +) -> (db::DatabasePool, kameo::actor::ActorRef, oneshot::Receiver>) { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); @@ -30,20 +34,26 @@ async fn setup_sealed_user_agent( .unwrap(); actors.vault.ask(Seal).await.unwrap(); - let session = UserAgentSession::spawn(UserAgentSession::new_test(db.clone(), actors)); + let (promotion_tx, promotion_rx) = oneshot::channel(); + let pubkey = authn::SigningKey::generate().public_key(); + let auth_creds = AuthCredentials { + creds: Credentials { id: 1, pubkey }, + new_nonce: 1, + }; + let gate = VaultGate::spawn(VaultGate::new(auth_creds, actors, db.clone(), promotion_tx)); - (db, session) + (db, gate, promotion_rx) } async fn client_dh_encrypt( - user_agent: &kameo::actor::ActorRef, + gate: &kameo::actor::ActorRef, key_to_send: &[u8], ) -> HandleUnsealEncryptedKey { let client_secret = EphemeralSecret::random(); let client_public = PublicKey::from(&client_secret); - let response = user_agent - .ask(HandleUnsealRequest { + let response = gate + .ask(HandleHandshake { client_pubkey: client_public, }) .await @@ -71,26 +81,26 @@ async fn client_dh_encrypt( #[test_log::test] pub async fn test_unseal_success() { let seal_key = b"test-seal-key"; - let (_db, user_agent) = setup_sealed_user_agent(seal_key).await; + let (_db, gate, _promotion_rx) = setup_sealed_gate(seal_key).await; - let encrypted_key = client_dh_encrypt(&user_agent, seal_key).await; + let encrypted_key = client_dh_encrypt(&gate, seal_key).await; - let response = user_agent.ask(encrypted_key).await; + let response = gate.ask(encrypted_key).await; assert!(matches!(response, Ok(()))); } #[tokio::test] #[test_log::test] pub async fn test_unseal_wrong_seal_key() { - let (_db, user_agent) = setup_sealed_user_agent(b"correct-key").await; + let (_db, gate, _promotion_rx) = setup_sealed_gate(b"correct-key").await; - let encrypted_key = client_dh_encrypt(&user_agent, b"wrong-key").await; + let encrypted_key = client_dh_encrypt(&gate, b"wrong-key").await; - let response = user_agent.ask(encrypted_key).await; + let response = gate.ask(encrypted_key).await; assert!(matches!( response, Err(kameo::error::SendError::HandlerError( - UnsealError::InvalidKey + VaultGateError::InvalidKey )) )); } @@ -98,19 +108,18 @@ pub async fn test_unseal_wrong_seal_key() { #[tokio::test] #[test_log::test] pub async fn test_unseal_corrupted_ciphertext() { - let (_db, user_agent) = setup_sealed_user_agent(b"test-key").await; + let (_db, gate, _promotion_rx) = setup_sealed_gate(b"test-key").await; let client_secret = EphemeralSecret::random(); let client_public = PublicKey::from(&client_secret); - user_agent - .ask(HandleUnsealRequest { - client_pubkey: client_public, - }) - .await - .unwrap(); + gate.ask(HandleHandshake { + client_pubkey: client_public, + }) + .await + .unwrap(); - let response = user_agent + let response = gate .ask(HandleUnsealEncryptedKey { nonce: vec![0u8; 24], ciphertext: vec![0u8; 32], @@ -121,7 +130,7 @@ pub async fn test_unseal_corrupted_ciphertext() { assert!(matches!( response, Err(kameo::error::SendError::HandlerError( - UnsealError::InvalidKey + VaultGateError::InvalidKey )) )); } @@ -130,24 +139,24 @@ pub async fn test_unseal_corrupted_ciphertext() { #[test_log::test] pub async fn test_unseal_retry_after_invalid_key() { let seal_key = b"real-seal-key"; - let (_db, user_agent) = setup_sealed_user_agent(seal_key).await; + let (_db, gate, _promotion_rx) = setup_sealed_gate(seal_key).await; { - let encrypted_key = client_dh_encrypt(&user_agent, b"wrong-key").await; + let encrypted_key = client_dh_encrypt(&gate, b"wrong-key").await; - let response = user_agent.ask(encrypted_key).await; + let response = gate.ask(encrypted_key).await; assert!(matches!( response, Err(kameo::error::SendError::HandlerError( - UnsealError::InvalidKey + VaultGateError::InvalidKey )) )); } { - let encrypted_key = client_dh_encrypt(&user_agent, seal_key).await; + let encrypted_key = client_dh_encrypt(&gate, seal_key).await; - let response = user_agent.ask(encrypted_key).await; + let response = gate.ask(encrypted_key).await; assert!(matches!(response, Ok(()))); } } From f49e995c2f6f3e8b47b420fc5948822a4e625b4e Mon Sep 17 00:00:00 2001 From: hdbg Date: Sun, 12 Apr 2026 12:04:03 +0200 Subject: [PATCH 06/13] WIP: `kameo::messages` wiring for transport generalization --- server/Cargo.lock | 763 +++++++++++------- server/Cargo.toml | 4 +- server/crates/arbiter-proto/src/transport.rs | 57 ++ server/crates/arbiter-server/Cargo.toml | 2 +- .../arbiter-server/src/grpc/user_agent.rs | 130 +-- .../src/grpc/user_agent/auth.rs | 34 +- .../src/grpc/user_agent/vault_gate.rs | 309 ++++--- .../src/peers/user_agent/auth/mod.rs | 4 +- .../src/peers/user_agent/auth/state.rs | 10 +- .../src/peers/user_agent/mod.rs | 118 ++- .../src/peers/user_agent/vault_gate/mod.rs | 4 +- .../src/peers/user_agent/vault_gate/state.rs | 13 +- .../arbiter-server/tests/user_agent/auth.rs | 20 +- 13 files changed, 930 insertions(+), 538 deletions(-) diff --git a/server/Cargo.lock b/server/Cargo.lock index 3787bcb..7ec7471 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -44,9 +44,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4973038846323e4e69a433916522195dce2947770076c03078fc21c80ea0f1c4" +checksum = "50ab0cd8afe573d1f7dc2353698a51b1f93aec362c8211e28cfd3948c6adba39" dependencies = [ "alloy-consensus", "alloy-contract", @@ -67,9 +67,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives", "num_enum", @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" dependencies = [ "alloy-consensus", "alloy-eips", @@ -119,9 +119,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7" +checksum = "7ac9e0c34dc6bce643b182049cdfcca1b8ce7d9c260cbdd561f511873b7e26cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -137,6 +137,7 @@ dependencies = [ "futures-util", "serde_json", "thiserror 2.0.18", + "tracing", ] [[package]] @@ -220,9 +221,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -239,14 +240,13 @@ dependencies = [ "serde", "serde_with", "sha2 0.10.9", - "thiserror 2.0.18", ] [[package]] name = "alloy-genesis" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" dependencies = [ "alloy-eips", "alloy-primitives", @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" dependencies = [ "alloy-consensus", "alloy-eips", @@ -336,13 +336,13 @@ dependencies = [ "derive_more", "foldhash 0.2.0", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.2", + "rand 0.9.4", "rapidhash", "ruint", "rustc-hash", @@ -352,9 +352,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" dependencies = [ "alloy-chains", "alloy-consensus", @@ -391,9 +391,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" dependencies = [ "proc-macro2", "quote", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -448,9 +448,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -471,7 +471,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "serde_with", @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" dependencies = [ "alloy-primitives", "serde", @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" dependencies = [ "alloy-primitives", "async-trait", @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" dependencies = [ "alloy-consensus", "alloy-network", @@ -544,7 +544,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -618,13 +618,13 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" dependencies = [ "alloy-json-rpc", "alloy-transport", - "itertools 0.13.0", + "itertools 0.14.0", "reqwest", "serde_json", "tower", @@ -650,11 +650,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -684,7 +684,7 @@ dependencies = [ "arbiter-proto", "async-trait", "http", - "rand 0.10.0", + "rand 0.10.1", "rustls-webpki", "thiserror 2.0.18", "tokio", @@ -699,7 +699,7 @@ dependencies = [ "base64", "memsafe", "ml-dsa", - "rand 0.10.0", + "rand 0.10.1", "x-wing", ] @@ -716,7 +716,7 @@ dependencies = [ "prost", "prost-types", "protoc-bin-vendored", - "rand 0.10.0", + "rand 0.10.1", "rcgen", "rstest", "rustls-pki-types", @@ -761,7 +761,7 @@ dependencies = [ "proptest", "prost", "prost-types", - "rand 0.10.0", + "rand 0.10.1", "rcgen", "restructed", "rstest", @@ -1107,9 +1107,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -1119,9 +1119,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -1370,9 +1370,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -1380,6 +1380,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -1411,7 +1417,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -1454,9 +1460,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -1468,15 +1474,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" [[package]] -name = "console" -version = "0.15.11" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1532,6 +1547,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1622,7 +1647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -1706,7 +1731,6 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "serde", "strsim", "syn 2.0.117", ] @@ -1720,6 +1744,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -2015,8 +2040,8 @@ checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ "curve25519-dalek 5.0.0-pre.6", "ed25519", - "rand_core 0.10.0", - "sha2 0.11.0-rc.5", + "rand_core 0.10.1", + "sha2 0.11.0", "subtle", "zeroize", ] @@ -2121,9 +2146,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -2164,7 +2189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro-crate", "proc-macro2", "quote", @@ -2432,7 +2457,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2472,7 +2497,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -2513,6 +2538,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.5.0" @@ -2596,9 +2627,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "ctutils", "typenum", @@ -2607,9 +2638,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -2622,7 +2653,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2630,19 +2660,17 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] @@ -2707,12 +2735,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2720,9 +2749,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2733,9 +2762,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2747,15 +2776,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2767,15 +2796,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2852,12 +2881,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -2873,9 +2902,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.3" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "once_cell", @@ -2891,9 +2920,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -2923,12 +2952,65 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -2941,10 +3023,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2967,8 +3051,7 @@ dependencies = [ [[package]] name = "kameo" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dfd134d7a2c6ec05ee696dcbf3f7a034bdb97ecc623e981014652dcd124d77" +source = "git+https://github.com/hdbg/kameo.git?rev=805b417#805b41783fe90b54827ecad142b422c7a9b69b9a" dependencies = [ "downcast-rs", "dyn-clone", @@ -2982,8 +3065,7 @@ dependencies = [ [[package]] name = "kameo_actors" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220bdd75769f0a9b752a91123e58bf42f595e3c36ff4b13818d7a87d962076e6" +source = "git+https://github.com/hdbg/kameo.git?rev=805b417#805b41783fe90b54827ecad142b422c7a9b69b9a" dependencies = [ "futures", "glob", @@ -2995,9 +3077,9 @@ dependencies = [ [[package]] name = "kameo_macros" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c9002c9ecd16e1636f566c0bf62db48e70d86ed0d0a91b398955e883217a23" +source = "git+https://github.com/hdbg/kameo.git?rev=805b417#805b41783fe90b54827ecad142b422c7a9b69b9a" dependencies = [ + "darling 0.23.0", "heck", "proc-macro2", "quote", @@ -3025,9 +3107,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3040,7 +3122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" dependencies = [ "crypto-common 0.2.1", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -3057,9 +3139,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libm" @@ -3085,9 +3167,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -3106,9 +3188,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -3237,9 +3319,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -3257,7 +3339,7 @@ dependencies = [ "hybrid-array", "module-lattice", "pkcs8 0.11.0-rc.11", - "rand_core 0.10.0", + "rand_core 0.10.1", "sha3 0.11.0", "signature 3.0.0-rc.10", "zeroize", @@ -3272,7 +3354,7 @@ dependencies = [ "hybrid-array", "kem", "module-lattice", - "rand_core 0.10.0", + "rand_core 0.10.1", "sha3 0.11.0", "zeroize", ] @@ -3332,9 +3414,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -3430,6 +3512,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "owo-colors" version = "4.3.0" @@ -3538,7 +3626,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -3595,9 +3683,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "poly1305" @@ -3618,9 +3706,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -3735,7 +3823,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -3761,7 +3849,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.13.0", + "itertools 0.14.0", "log", "multimap", "petgraph", @@ -3782,7 +3870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -3864,9 +3952,9 @@ checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" [[package]] name = "pulldown-cmark" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14104c5a24d9bcf7eb2c24753e0f49fe14555d8bd565ea3d38e4b4303267259d" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" dependencies = [ "bitflags", "memchr", @@ -3914,10 +4002,11 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -3984,9 +4073,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3995,13 +4084,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20 0.10.0", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -4045,9 +4134,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xorshift" @@ -4148,9 +4237,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", @@ -4168,9 +4257,9 @@ dependencies = [ "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", @@ -4181,7 +4270,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", ] [[package]] @@ -4289,7 +4377,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.2", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -4311,9 +4399,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-hex" @@ -4336,7 +4424,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.28", ] [[package]] @@ -4363,20 +4451,31 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -4388,10 +4487,37 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.103.10" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" dependencies = [ "aws-lc-rs", "ring", @@ -4418,10 +4544,22 @@ dependencies = [ ] [[package]] -name = "ryu" -version = "1.0.23" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] name = "schemars" @@ -4507,6 +4645,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -4518,9 +4679,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -4576,25 +4737,13 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_with" version = "3.18.0" @@ -4605,7 +4754,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -4649,12 +4798,12 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "digest 0.11.2", ] @@ -4680,9 +4829,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -4730,14 +4879,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" dependencies = [ "digest 0.11.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "similar" @@ -4991,12 +5140,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5121,9 +5270,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -5146,9 +5295,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -5164,9 +5313,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -5232,32 +5381,32 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.1+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.1", ] [[package]] @@ -5339,7 +5488,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -5497,9 +5646,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -5562,9 +5711,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5597,6 +5746,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5632,9 +5791,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -5645,23 +5804,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5669,9 +5824,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -5682,9 +5837,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -5706,7 +5861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -5719,8 +5874,8 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -5739,9 +5894,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5758,10 +5913,10 @@ dependencies = [ ] [[package]] -name = "webpki-roots" +name = "webpki-root-certs" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -5782,6 +5937,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5849,18 +6013,18 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.42.2", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] @@ -5883,6 +6047,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -5916,6 +6095,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5928,6 +6113,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5940,6 +6131,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5964,6 +6161,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5976,6 +6179,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5988,6 +6197,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6000,6 +6215,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6023,9 +6244,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -6058,7 +6279,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -6089,7 +6310,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -6108,9 +6329,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -6120,9 +6341,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -6141,7 +6362,7 @@ checksum = "e17d0d5f4d1f26b9b9e7477af1d3bef960e1d1fb64edab7912fde472a8a8432e" dependencies = [ "kem", "ml-kem", - "rand_core 0.10.0", + "rand_core 0.10.1", "sha3 0.11.0", "x25519-dalek 3.0.0-pre.6", "zeroize", @@ -6166,7 +6387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d5d6ff67acd3945b933e592bfa7143db4fcbb2f871754b6b9fbd7847fc5aea" dependencies = [ "curve25519-dalek 5.0.0-pre.6", - "rand_core 0.10.0", + "rand_core 0.10.1", "zeroize", ] @@ -6199,9 +6420,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -6210,9 +6431,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -6222,18 +6443,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -6242,18 +6463,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -6283,9 +6504,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -6294,9 +6515,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -6305,9 +6526,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/server/Cargo.toml b/server/Cargo.toml index 7727573..174c5ef 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,7 +26,6 @@ thiserror = "2.0.18" async-trait = "0.1.89" futures = "0.3.32" tokio-stream = { version = "0.1.18", features = ["full"] } -kameo = "0.20" prost-types = { version = "0.14.3", features = ["chrono"] } x25519-dalek = { version = "2.0.1", features = ["getrandom"] } rstest = "0.26.1" @@ -47,3 +46,6 @@ miette = { version = "7.6.0", features = ["fancy", "serde"] } mutants = "0.0.4" ml-dsa = { version = "0.1.0-rc.8", features = ["zeroize"] } base64 = "0.22.1" +kameo = {git = "https://github.com/hdbg/kameo.git", rev = "805b417"} +kameo_actors = {git = "https://github.com/hdbg/kameo.git", rev = "805b417"} + diff --git a/server/crates/arbiter-proto/src/transport.rs b/server/crates/arbiter-proto/src/transport.rs index 5f8b5b4..dcf517b 100644 --- a/server/crates/arbiter-proto/src/transport.rs +++ b/server/crates/arbiter-proto/src/transport.rs @@ -58,6 +58,7 @@ use std::marker::PhantomData; use async_trait::async_trait; +use kameo::{error::Infallible, prelude::*}; /// Errors returned by transport adapters implementing [`Bi`]. #[derive(thiserror::Error, Debug)] @@ -106,6 +107,36 @@ pub trait Receiver: Send + Sync { /// any built-in correlation mechanism between inbound and outbound items. pub trait Bi: Sender + Receiver + Send + Sync {} +#[async_trait] +impl Sender for &mut T +where + T: Sender + ?Sized, + Outbound: Send + 'static, +{ + async fn send(&mut self, item: Outbound) -> Result<(), Error> { + (**self).send(item).await + } +} + +#[async_trait] +impl Receiver for &mut T +where + T: Receiver + ?Sized, + Inbound: Send + 'static, +{ + async fn recv(&mut self) -> Option { + (**self).recv().await + } +} + +impl Bi for &mut T +where + T: Bi + ?Sized, + Inbound: Send + 'static, + Outbound: Send + 'static, +{ +} + pub trait SplittableBi: Bi { type Sender: Sender; type Receiver: Receiver; @@ -161,3 +192,29 @@ where } pub mod grpc; + +#[derive(thiserror::Error, Debug)] +pub enum ForwardError { + #[error("Transport error: {0}")] + Transport(#[from] Error), + #[error("Actor delivery error: {0}")] + Actor(SendError), +} + +pub async fn forward_to_actor( + transport: &mut Transport, + actor: &ActorRef, +) -> Result<(), ForwardError> +where + Transport: Bi::Ok>, + Handler: Actor + Message, + Inbound: Send + 'static, + Outbound: Send + 'static + Reply, // `Infallible` to enforce contract that `Outbound` carries handler-level error +{ + while let Some(request) = transport.recv().await { + let resp = actor.ask(request).await.map_err(ForwardError::Actor)?; + transport.send(resp).await? + } + + Err(Error::ChannelClosed.into()) +} diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index f7272f6..adc046e 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -58,7 +58,7 @@ ml-dsa.workspace = true ed25519-dalek.workspace = true x25519-dalek.workspace = true k256.workspace = true -kameo_actors = "0.5.0" +kameo_actors.workspace = true [dev-dependencies] insta = "1.46.3" diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index 403163e..49c13ba 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -1,4 +1,4 @@ -use tokio::sync::{mpsc, oneshot}; +use tokio::sync::mpsc; use arbiter_proto::{ proto::user_agent::{ @@ -9,17 +9,13 @@ use arbiter_proto::{ transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi}, }; use async_trait::async_trait; -use kameo::actor::{ActorRef, Spawn as _}; +use kameo::actor::ActorRef; use tonic::Status; use tracing::{error, info, warn}; use crate::{ - crypto::integrity, grpc::request_tracker::RequestTracker, - peers::user_agent::{ - Credentials, OutOfBand, UserAgentConnection, UserAgentSession, - vault_gate::VaultGate, - }, + peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, }; mod auth; @@ -129,115 +125,23 @@ pub async fn start( ) { let mut request_tracker = RequestTracker::default(); - let auth_creds = match auth::start(&mut conn, &mut bi, &mut request_tracker).await { - Ok(creds) => creds, - Err(e) => { - warn!(error = ?e, "Authentication failed"); - return; - } - }; - - info!(pubkey = ?auth_creds.creds.pubkey, "User authenticated successfully"); - - let creds = if integrity::is_signing_available(&conn.actors.vault) - .await - .unwrap_or(false) - { - // Vault is unsealed; integrity was verified during auth — promote directly. - auth_creds.creds - } else { - // Vault is sealed/unbootstrapped; run the VaultGate phase. - let (promotion_tx, promotion_rx) = oneshot::channel(); - let gate = VaultGate::spawn(VaultGate::new( - auth_creds, - conn.actors.clone(), - conn.db.clone(), - promotion_tx, - )); - - let result = vault_gate_loop(&mut bi, &gate, &mut request_tracker, promotion_rx).await; - gate.kill(); - - match result { - Some(creds) => creds, - None => return, - } - }; - let (oob_sender, oob_receiver) = mpsc::channel(16); let oob_adapter = OutOfBandAdapter(oob_sender); - let actor = UserAgentSession::spawn(UserAgentSession::new(conn, creds, Box::new(oob_adapter))); - let actor_for_cleanup = actor.clone(); - - dispatch_loop(bi, actor, oob_receiver, request_tracker).await; - actor_for_cleanup.kill(); -} - -async fn vault_gate_loop( - bi: &mut GrpcBi, - gate: &ActorRef, - request_tracker: &mut RequestTracker, - mut promotion_rx: oneshot::Receiver>, -) -> Option { - loop { - tokio::select! { - result = &mut promotion_rx => { - return match result { - Ok(Ok(creds)) => Some(creds), - Ok(Err(e)) => { - warn!(error = ?e, "VaultGate promotion failed"); - None - } - Err(_) => { - warn!("VaultGate promotion channel closed unexpectedly"); - None - } - }; - } - - message = bi.recv() => { - let Some(message) = message else { return None; }; - - let conn = match message { - Ok(conn) => conn, - Err(err) => { - warn!(error = ?err, "Failed to receive request during vault gate phase"); - return None; - } - }; - - let request_id = match request_tracker.request(conn.id) { - Ok(id) => id, - Err(err) => { - let _ = bi.send(Err(err)).await; - return None; - } - }; - - let Some(payload) = conn.payload else { - let _ = bi.send(Err(Status::invalid_argument("Missing request payload"))).await; - return None; - }; - - let response = match payload { - UserAgentRequestPayload::Vault(req) => vault_gate::dispatch(gate, req).await, - _ => Err(Status::permission_denied("Only vault operations are permitted before unsealing")), - }; - - match response { - Ok(Some(payload)) => { - if bi.send(Ok(UserAgentResponse { id: Some(request_id), payload: Some(payload) })).await.is_err() { - return None; - } - } - Ok(None) => {} - Err(status) => { - let _ = bi.send(Err(status)).await; - return None; - } - } + let actor = { + let transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker); + match crate::peers::user_agent::start(&mut conn, transport, Box::new(oob_adapter)).await { + Ok(actor) => actor, + Err(e) => { + warn!(error = ?e, "User agent connection failed"); + return; } } - } + }; + + info!("User agent session established"); + + dispatch_loop(bi, actor.clone(), oob_receiver, request_tracker).await; + actor.kill(); } + diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs index ab8d70e..53ee57d 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs @@ -23,8 +23,8 @@ use crate::{ }; pub struct AuthTransportAdapter<'a> { - bi: &'a mut GrpcBi, - request_tracker: &'a mut RequestTracker, + pub(super) bi: &'a mut GrpcBi, + pub(super) request_tracker: &'a mut RequestTracker, } impl<'a> AuthTransportAdapter<'a> { @@ -38,19 +38,35 @@ impl<'a> AuthTransportAdapter<'a> { } } - async fn send_user_agent_response( + pub(super) fn bi_mut(&mut self) -> &mut GrpcBi { + self.bi + } + + pub(super) fn tracker_mut(&mut self) -> &mut RequestTracker { + self.request_tracker + } + + pub(super) async fn send_response_payload( &mut self, - payload: AuthResponsePayload, + payload: UserAgentResponsePayload, ) -> Result<(), TransportError> { self.bi .send(Ok(UserAgentResponse { id: Some(self.request_tracker.current_request_id()), - payload: Some(UserAgentResponsePayload::Auth(proto_auth::Response { - payload: Some(payload), - })), + payload: Some(payload), })) .await } + + async fn send_user_agent_response( + &mut self, + payload: AuthResponsePayload, + ) -> Result<(), TransportError> { + self.send_response_payload(UserAgentResponsePayload::Auth(proto_auth::Response { + payload: Some(payload), + })) + .await + } } #[async_trait] @@ -168,6 +184,6 @@ pub async fn start( bi: &mut GrpcBi, request_tracker: &mut RequestTracker, ) -> Result { - let transport = AuthTransportAdapter::new(bi, request_tracker); - auth::authenticate(conn, transport).await + let mut transport = AuthTransportAdapter::new(bi, request_tracker); + auth::authenticate(conn, &mut transport).await } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs index 2436064..03a38f1 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs @@ -1,25 +1,28 @@ -use arbiter_proto::proto::user_agent::{ - user_agent_response::Payload as UserAgentResponsePayload, - vault::{ - self as proto_vault, - bootstrap::{ - self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult, - }, - request::Payload as VaultRequestPayload, - response::Payload as VaultResponsePayload, - unseal::{ - self as proto_unseal, UnsealResult as ProtoUnsealResult, UnsealStart, - request::Payload as UnsealRequestPayload, response::Payload as UnsealResponsePayload, +use arbiter_proto::{ + proto::user_agent::{ + user_agent_request::Payload as UserAgentRequestPayload, + user_agent_response::Payload as UserAgentResponsePayload, + vault::{ + self as proto_vault, + bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, + request::Payload as VaultRequestPayload, + response::Payload as VaultResponsePayload, + unseal::{ + self as proto_unseal, UnsealResult as ProtoUnsealResult, + request::Payload as UnsealRequestPayload, + response::Payload as UnsealResponsePayload, + }, }, }, + transport::{Bi, Error as TransportError, Receiver, Sender}, }; -use kameo::{actor::ActorRef, error::SendError}; +use async_trait::async_trait; use tonic::Status; use tracing::warn; +use super::auth::AuthTransportAdapter; use crate::peers::user_agent::vault_gate::{ self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, - VaultGate, }; fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { @@ -40,112 +43,196 @@ fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePay })) } -pub(super) async fn dispatch( - gate: &ActorRef, - req: proto_vault::Request, -) -> Result, Status> { - let Some(payload) = req.payload else { - return Err(Status::invalid_argument("Missing vault request payload")); - }; - - match payload { - VaultRequestPayload::QueryState(_) => { - use arbiter_proto::proto::shared::VaultState as ProtoVaultState; - Ok(Some(wrap_vault_response(VaultResponsePayload::State( - ProtoVaultState::Sealed.into(), - )))) - } - VaultRequestPayload::Unseal(req) => dispatch_unseal(gate, req).await, - VaultRequestPayload::Bootstrap(req) => dispatch_bootstrap(gate, req).await, +impl AuthTransportAdapter<'_> { + async fn send_query_state(&mut self) -> Result<(), TransportError> { + use arbiter_proto::proto::shared::VaultState as ProtoVaultState; + self.send_response_payload(wrap_vault_response(VaultResponsePayload::State( + ProtoVaultState::Sealed.into(), + ))) + .await } } -async fn dispatch_unseal( - gate: &ActorRef, - req: proto_unseal::Request, -) -> Result, Status> { - let Some(payload) = req.payload else { - return Err(Status::invalid_argument("Missing unseal request payload")); - }; +#[async_trait] +impl Receiver for AuthTransportAdapter<'_> { + async fn recv(&mut self) -> Option { + loop { + let request = match self.bi_mut().recv().await? { + Ok(request) => request, + Err(error) => { + warn!(?error, "Failed to receive user agent request during vault gate"); + return None; + } + }; - match payload { - UnsealRequestPayload::Start(req) => handle_unseal_start(gate, req).await, - UnsealRequestPayload::EncryptedKey(req) => handle_unseal_encrypted_key(gate, req).await, + if let Err(err) = self.tracker_mut().request(request.id) { + let _ = self.bi_mut().send(Err(err)).await; + return None; + } + + let Some(payload) = request.payload else { + let _ = self + .bi_mut() + .send(Err(Status::invalid_argument("Missing request payload"))) + .await; + return None; + }; + + let vault_req = match payload { + UserAgentRequestPayload::Vault(req) => req, + _ => { + let _ = self + .bi_mut() + .send(Err(Status::permission_denied( + "Only vault operations are permitted before unsealing", + ))) + .await; + return None; + } + }; + + let Some(vault_payload) = vault_req.payload else { + let _ = self + .bi_mut() + .send(Err(Status::invalid_argument("Missing vault request payload"))) + .await; + return None; + }; + + match vault_payload { + VaultRequestPayload::QueryState(_) => { + if self.send_query_state().await.is_err() { + return None; + } + continue; + } + VaultRequestPayload::Unseal(req) => { + let Some(unseal_payload) = req.payload else { + let _ = self + .bi_mut() + .send(Err(Status::invalid_argument("Missing unseal request payload"))) + .await; + return None; + }; + match unseal_payload { + UnsealRequestPayload::Start(start) => { + let Ok(bytes) = <[u8; 32]>::try_from(start.client_pubkey) else { + let _ = self + .bi_mut() + .send(Err(Status::invalid_argument( + "Invalid X25519 public key", + ))) + .await; + return None; + }; + return Some(vault_gate::Inbound::HandleHandshake(HandleHandshake { + client_pubkey: x25519_dalek::PublicKey::from(bytes), + })); + } + UnsealRequestPayload::EncryptedKey(key) => { + return Some(vault_gate::Inbound::HandleUnsealEncryptedKey( + HandleUnsealEncryptedKey { + nonce: key.nonce, + ciphertext: key.ciphertext, + associated_data: key.associated_data, + }, + )); + } + } + } + VaultRequestPayload::Bootstrap(req) => { + let Some(encrypted_key) = req.encrypted_key else { + let _ = self + .bi_mut() + .send(Err(Status::invalid_argument( + "Missing bootstrap encrypted key", + ))) + .await; + return None; + }; + return Some(vault_gate::Inbound::HandleBootstrapEncryptedKey( + HandleBootstrapEncryptedKey { + nonce: encrypted_key.nonce, + ciphertext: encrypted_key.ciphertext, + associated_data: encrypted_key.associated_data, + }, + )); + } + } + } } } -async fn handle_unseal_start( - gate: &ActorRef, - req: UnsealStart, -) -> Result, Status> { - let client_pubkey = <[u8; 32]>::try_from(req.client_pubkey) - .map(x25519_dalek::PublicKey::from) - .map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?; +#[async_trait] +impl Sender> for AuthTransportAdapter<'_> { + async fn send( + &mut self, + item: Result, + ) -> Result<(), TransportError> { + let outbound = match item { + Ok(outbound) => outbound, + Err(err) => { + warn!(?err, "vault gate produced transport-level error"); + return self + .bi_mut() + .send(Err(Status::internal(err.to_string()))) + .await; + } + }; - let response = gate - .ask(HandleHandshake { client_pubkey }) - .await - .map_err(|err| { - warn!(error = ?err, "Failed to handle unseal start"); - Status::internal("Failed to start unseal flow") - })?; + let payload = match outbound { + vault_gate::Outbound::HandleHandshake(Ok(response)) => { + wrap_unseal_response(UnsealResponsePayload::Start( + proto_unseal::UnsealStartResponse { + server_pubkey: response.server_pubkey.as_bytes().to_vec(), + }, + )) + } + vault_gate::Outbound::HandleHandshake(Err(err)) => { + warn!(?err, "handshake failed"); + return self + .bi_mut() + .send(Err(Status::internal("Failed to start unseal flow"))) + .await; + } + vault_gate::Outbound::HandleUnsealEncryptedKey(result) => { + let proto_result = match result { + Ok(()) => ProtoUnsealResult::Success, + Err(vault_gate::Error::InvalidKey) => ProtoUnsealResult::InvalidKey, + Err(err) => { + warn!(?err, "unseal failed"); + return self + .bi_mut() + .send(Err(Status::internal("Failed to unseal vault"))) + .await; + } + }; + wrap_unseal_response(UnsealResponsePayload::Result(proto_result.into())) + } + vault_gate::Outbound::HandleBootstrapEncryptedKey(result) => { + let proto_result = match result { + Ok(()) => ProtoBootstrapResult::Success, + Err(vault_gate::Error::InvalidKey) => ProtoBootstrapResult::InvalidKey, + Err(vault_gate::Error::AlreadyBootstrapped) => { + ProtoBootstrapResult::AlreadyBootstrapped + } + Err(err) => { + warn!(?err, "bootstrap failed"); + return self + .bi_mut() + .send(Err(Status::internal("Failed to bootstrap vault"))) + .await; + } + }; + wrap_bootstrap_response(proto_result) + } + }; - Ok(Some(wrap_unseal_response(UnsealResponsePayload::Start( - proto_unseal::UnsealStartResponse { - server_pubkey: response.server_pubkey.as_bytes().to_vec(), - }, - )))) + self.send_response_payload(payload).await + } } -async fn handle_unseal_encrypted_key( - gate: &ActorRef, - req: arbiter_proto::proto::user_agent::vault::unseal::UnsealEncryptedKey, -) -> Result, Status> { - let result = match gate - .ask(HandleUnsealEncryptedKey { - nonce: req.nonce, - ciphertext: req.ciphertext, - associated_data: req.associated_data, - }) - .await - { - Ok(()) => ProtoUnsealResult::Success, - Err(SendError::HandlerError(vault_gate::Error::InvalidKey)) => ProtoUnsealResult::InvalidKey, - Err(err) => { - warn!(error = ?err, "Failed to handle unseal request"); - return Err(Status::internal("Failed to unseal vault")); - } - }; - Ok(Some(wrap_unseal_response(UnsealResponsePayload::Result( - result.into(), - )))) -} - -async fn dispatch_bootstrap( - gate: &ActorRef, - req: proto_bootstrap::Request, -) -> Result, Status> { - let encrypted_key = req - .encrypted_key - .ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))?; - - let result = match gate - .ask(HandleBootstrapEncryptedKey { - nonce: encrypted_key.nonce, - ciphertext: encrypted_key.ciphertext, - associated_data: encrypted_key.associated_data, - }) - .await - { - Ok(()) => ProtoBootstrapResult::Success, - Err(SendError::HandlerError(vault_gate::Error::InvalidKey)) => ProtoBootstrapResult::InvalidKey, - Err(SendError::HandlerError(vault_gate::Error::AlreadyBootstrapped)) => { - ProtoBootstrapResult::AlreadyBootstrapped - } - Err(err) => { - warn!(error = ?err, "Failed to handle bootstrap request"); - return Err(Status::internal("Failed to bootstrap vault")); - } - }; - Ok(Some(wrap_bootstrap_response(result))) +impl Bi> + for AuthTransportAdapter<'_> +{ } diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs index a30bc5e..97a510a 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs @@ -68,10 +68,10 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { pub async fn authenticate( props: &mut UserAgentConnection, - transport: T, + transport: &mut T, ) -> Result where - T: Bi> + Send, + T: Bi> + Send + ?Sized, { let mut state = AuthStateMachine::new(AuthContext::new(props, transport)); diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index 7f9dbb5..bb48291 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -174,20 +174,20 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result { +pub struct AuthContext<'a, T: ?Sized> { pub(super) conn: &'a mut UserAgentConnection, - pub(super) transport: T, + pub(super) transport: &'a mut T, } -impl<'a, T> AuthContext<'a, T> { - pub fn new(conn: &'a mut UserAgentConnection, transport: T) -> Self { +impl<'a, T: ?Sized> AuthContext<'a, T> { + pub fn new(conn: &'a mut UserAgentConnection, transport: &'a mut T) -> Self { Self { conn, transport } } } impl AuthStateMachineContext for AuthContext<'_, T> where - T: Bi> + Send, + T: Bi> + Send + ?Sized, { type Error = Error; diff --git a/server/crates/arbiter-server/src/peers/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/mod.rs index 5601101..a8e813c 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/mod.rs @@ -1,13 +1,25 @@ use crate::{ - actors::GlobalActors, crypto::integrity::Integrable, db, peers::client::ClientProfile, + actors::GlobalActors, + crypto::integrity::{self, Integrable}, + db::{self, DatabaseError}, + peers::client::ClientProfile, }; use arbiter_crypto::authn; +use arbiter_proto::transport::{Bi, Sender}; +pub use auth::authenticate; +use kameo::actor::{ActorRef, Spawn as _}; +pub use session::UserAgentSession; +use tokio::sync::oneshot; +use tracing::warn; +use vault_gate::VaultGate; + +use crate::crypto::integrity::hashing::Hashable; + pub mod auth; pub mod session; pub mod vault_gate; - #[derive(Debug, Clone, Hash)] pub struct Credentials { pub id: i32, @@ -40,7 +52,6 @@ impl Hashable for AuthCredentials { } } - impl Integrable for AuthCredentials { const KIND: &'static str = "useragent_credentials"; } @@ -52,6 +63,7 @@ pub enum OutOfBand { ClientConnectionCancel { pubkey: authn::PublicKey }, } +#[derive(Clone)] pub struct UserAgentConnection { pub(crate) db: db::DatabasePool, pub(crate) actors: GlobalActors, @@ -63,9 +75,103 @@ impl UserAgentConnection { } } +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("authentication failed: {0:?}")] + Auth(auth::Error), + #[error("vault gate failed: {0}")] + VaultGate(#[from] vault_gate::Error), + #[error("transport closed unexpectedly")] + Transport, + #[error("database error: {0}")] + Database(DatabaseError), + #[error("internal: {0}")] + Internal(String), +} +impl From for Error { + fn from(err: auth::Error) -> Self { + Self::Auth(err) + } +} -pub use auth::authenticate; -pub use session::UserAgentSession; +pub async fn start( + props: &mut UserAgentConnection, + mut transport: T, + oob_sender: Box>, +) -> Result, Error> +where + T: Bi> + Send, + T: Bi> + Send, +{ + let auth_creds = authenticate(props, &mut transport).await?; -use crate::crypto::integrity::hashing::Hashable; \ No newline at end of file + let creds = match integrity::is_signing_available(&props.actors.vault) + .await + .map_err(|_| Error::Internal("Integrity verification failed".into()))? + { + // credentials were checked by `auth` stage + true => auth_creds.creds, + false => run_vault_gate(props, &mut transport, auth_creds).await?, + }; + + Ok(UserAgentSession::spawn(UserAgentSession::new( + props.clone(), + creds, + oob_sender, + ))) +} + +async fn run_vault_gate( + props: &UserAgentConnection, + transport: &mut T, + auth_creds: AuthCredentials, +) -> Result +where + T: Bi> + Send + ?Sized, +{ + let (promotion_tx, mut promotion_rx) = oneshot::channel(); + let gate = VaultGate::spawn(VaultGate::new( + auth_creds, + props.actors.clone(), + props.db.clone(), + promotion_tx, + )); + + let result = loop { + tokio::select! { + promotion = &mut promotion_rx => { + break match promotion { + Ok(Ok(creds)) => Ok(creds), + Ok(Err(err)) => Err(Error::VaultGate(err)), + Err(_) => Err(Error::Internal( + "vault gate promotion channel closed".into(), + )), + }; + } + + inbound = transport.recv() => { + let Some(inbound) = inbound else { + break Err(Error::Transport); + }; + + match gate.ask(inbound).await { + Ok(outbound) => { + if transport.send(Ok(outbound)).await.is_err() { + break Err(Error::Transport); + } + } + Err(err) => { + warn!(?err, "VaultGate failed to handle message"); + break Err(Error::Internal(format!( + "vault gate ask failed: {err:?}" + ))); + } + } + } + } + }; + + gate.kill(); + result +} diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs index 806bd7e..02c5978 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs @@ -119,7 +119,7 @@ impl VaultGate { } } -#[messages] +#[messages(messages = Inbound, replies = Outbound)] impl VaultGate { #[message] pub async fn handle_handshake( @@ -185,7 +185,7 @@ impl VaultGate { } #[message] - pub(crate) async fn handle_bootstrap_encrypted_key( + pub async fn handle_bootstrap_encrypted_key( &mut self, nonce: Vec, ciphertext: Vec, diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs index 8a86c99..214f877 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs @@ -2,17 +2,16 @@ use std::sync::Mutex; use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; - - pub struct Handshake { client_pubkey: PublicKey, } - - #[derive(Default)] pub enum State { #[default] - Idle, - ReadyForExchange { server_key: PublicKey, secret: SharedSecret }, -} \ No newline at end of file + Idle, + ReadyForExchange { + server_key: PublicKey, + secret: SharedSecret, + }, +} diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index fb3a446..cfc4b53 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -42,11 +42,11 @@ pub async fn test_bootstrap_token_auth() { .unwrap(); let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap(); - let (server_transport, mut test_transport) = ChannelTransport::new(); + let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, server_transport).await + auth::authenticate(&mut props, &mut server_transport).await }); let new_key = MlDsa87::key_gen(&mut rand::rng()); @@ -84,11 +84,11 @@ pub async fn test_bootstrap_invalid_token_auth() { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); - let (server_transport, mut test_transport) = ChannelTransport::new(); + let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, server_transport).await + auth::authenticate(&mut props, &mut server_transport).await }); let new_key = MlDsa87::key_gen(&mut rand::rng()); @@ -157,11 +157,11 @@ pub async fn test_challenge_auth() { .unwrap(); } - let (server_transport, mut test_transport) = ChannelTransport::new(); + let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, server_transport).await + auth::authenticate(&mut props, &mut server_transport).await }); test_transport @@ -234,11 +234,11 @@ pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() .unwrap(); } - let (server_transport, mut test_transport) = ChannelTransport::new(); + let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, server_transport).await + auth::authenticate(&mut props, &mut server_transport).await }); test_transport @@ -298,11 +298,11 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { .unwrap(); } - let (server_transport, mut test_transport) = ChannelTransport::new(); + let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, server_transport).await + auth::authenticate(&mut props, &mut server_transport).await }); test_transport From a6f94e31159f28a36be126a09f7aea187c8c2ab6 Mon Sep 17 00:00:00 2001 From: Skipper Date: Thu, 16 Apr 2026 18:39:12 +0200 Subject: [PATCH 07/13] fix(server): sending fixed vault state when on stage --- .../src/grpc/user_agent/vault_gate.rs | 92 +++++++++------- .../src/peers/user_agent/vault_gate/mod.rs | 41 +++++-- useragent/rust/Cargo.lock | 104 +++++++++++++++++- 3 files changed, 190 insertions(+), 47 deletions(-) diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs index 03a38f1..381fc45 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs @@ -1,16 +1,19 @@ use arbiter_proto::{ - proto::user_agent::{ - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, - vault::{ - self as proto_vault, - bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, - request::Payload as VaultRequestPayload, - response::Payload as VaultResponsePayload, - unseal::{ - self as proto_unseal, UnsealResult as ProtoUnsealResult, - request::Payload as UnsealRequestPayload, - response::Payload as UnsealResponsePayload, + proto::{ + shared::VaultState as ProtoVaultState, + user_agent::{ + user_agent_request::Payload as UserAgentRequestPayload, + user_agent_response::Payload as UserAgentResponsePayload, + vault::{ + self as proto_vault, + bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, + request::Payload as VaultRequestPayload, + response::Payload as VaultResponsePayload, + unseal::{ + self as proto_unseal, UnsealResult as ProtoUnsealResult, + request::Payload as UnsealRequestPayload, + response::Payload as UnsealResponsePayload, + }, }, }, }, @@ -21,8 +24,11 @@ use tonic::Status; use tracing::warn; use super::auth::AuthTransportAdapter; -use crate::peers::user_agent::vault_gate::{ - self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, +use crate::{ + actors::vault::VaultState, + peers::user_agent::vault_gate::{ + self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, + }, }; fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { @@ -43,15 +49,7 @@ fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePay })) } -impl AuthTransportAdapter<'_> { - async fn send_query_state(&mut self) -> Result<(), TransportError> { - use arbiter_proto::proto::shared::VaultState as ProtoVaultState; - self.send_response_payload(wrap_vault_response(VaultResponsePayload::State( - ProtoVaultState::Sealed.into(), - ))) - .await - } -} +impl AuthTransportAdapter<'_> {} #[async_trait] impl Receiver for AuthTransportAdapter<'_> { @@ -60,7 +58,10 @@ impl Receiver for AuthTransportAdapter<'_> { let request = match self.bi_mut().recv().await? { Ok(request) => request, Err(error) => { - warn!(?error, "Failed to receive user agent request during vault gate"); + warn!( + ?error, + "Failed to receive user agent request during vault gate" + ); return None; } }; @@ -94,23 +95,24 @@ impl Receiver for AuthTransportAdapter<'_> { let Some(vault_payload) = vault_req.payload else { let _ = self .bi_mut() - .send(Err(Status::invalid_argument("Missing vault request payload"))) + .send(Err(Status::invalid_argument( + "Missing vault request payload", + ))) .await; return None; }; match vault_payload { VaultRequestPayload::QueryState(_) => { - if self.send_query_state().await.is_err() { - return None; - } - continue; + return Some(vault_gate::Inbound::HandleVaultState); } VaultRequestPayload::Unseal(req) => { let Some(unseal_payload) = req.payload else { let _ = self .bi_mut() - .send(Err(Status::invalid_argument("Missing unseal request payload"))) + .send(Err(Status::invalid_argument( + "Missing unseal request payload", + ))) .await; return None; }; @@ -181,13 +183,29 @@ impl Sender> for AuthTransportAd }; let payload = match outbound { - vault_gate::Outbound::HandleHandshake(Ok(response)) => { - wrap_unseal_response(UnsealResponsePayload::Start( - proto_unseal::UnsealStartResponse { - server_pubkey: response.server_pubkey.as_bytes().to_vec(), - }, - )) - } + vault_gate::Outbound::HandleVaultState(result) => match result { + Ok(state) => { + let state = match state { + VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped, + VaultState::Sealed => ProtoVaultState::Sealed, + VaultState::Unsealed => ProtoVaultState::Unsealed, + }; + + wrap_vault_response(VaultResponsePayload::State(state.into())) + } + Err(err) => { + warn!(?err, "vault state query failed"); + return self + .bi_mut() + .send(Err(Status::internal("Failed to query vault state"))) + .await; + } + }, + vault_gate::Outbound::HandleHandshake(Ok(response)) => wrap_unseal_response( + UnsealResponsePayload::Start(proto_unseal::UnsealStartResponse { + server_pubkey: response.server_pubkey.as_bytes().to_vec(), + }), + ), vault_gate::Outbound::HandleHandshake(Err(err)) => { warn!(?err, "handshake failed"); return self diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs index 02c5978..dfd461c 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs @@ -13,7 +13,7 @@ use super::{AuthCredentials, Credentials}; use crate::{ actors::{ GlobalActors, - vault::{self, Bootstrap, TryUnseal, events}, + vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events}, }, crypto::integrity::{self, AttestationStatus}, db::DatabasePool, @@ -228,6 +228,18 @@ impl VaultGate { } } } + + #[message] + pub async fn handle_vault_state(&mut self) -> Result { + let answer = self + .actors + .vault + .ask(GetState {}) + .await + .map_err(|_| Error::internal("failed to query vault"))?; + + Ok(answer) + } } impl Message for VaultGate { @@ -239,13 +251,22 @@ impl Message for VaultGate { ctx: &mut kameo::prelude::Context, ) -> Self::Reply { let result = async { - let mut conn = self.db.get().await.map_err(|_| Error::internal("DB unavailable"))?; - integrity::sign_entity(&mut conn, &self.actors.vault, &self.auth_creds, self.auth_creds.creds.id) + let mut conn = self + .db + .get() .await - .map_err(|e| { - error!(?e, "Failed to sign integrity envelope on bootstrap"); - Error::internal("Integrity sign failed") - })?; + .map_err(|_| Error::internal("DB unavailable"))?; + integrity::sign_entity( + &mut conn, + &self.actors.vault, + &self.auth_creds, + self.auth_creds.creds.id, + ) + .await + .map_err(|e| { + error!(?e, "Failed to sign integrity envelope on bootstrap"); + Error::internal("Integrity sign failed") + })?; Ok(self.auth_creds.creds.clone()) } .await; @@ -266,7 +287,11 @@ impl Message for VaultGate { ctx: &mut kameo::prelude::Context, ) -> Self::Reply { let result = async { - let mut conn = self.db.get().await.map_err(|_| Error::internal("DB unavailable"))?; + let mut conn = self + .db + .get() + .await + .map_err(|_| Error::internal("DB unavailable"))?; match integrity::verify_entity( &mut conn, &self.actors.vault, diff --git a/useragent/rust/Cargo.lock b/useragent/rust/Cargo.lock index 020b2fc..9b8db0c 100644 --- a/useragent/rust/Cargo.lock +++ b/useragent/rust/Cargo.lock @@ -236,6 +236,7 @@ dependencies = [ "memsafe", "ml-dsa", "rand", + "x-wing", ] [[package]] @@ -713,7 +714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "rand_core", ] @@ -837,6 +838,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -884,6 +894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", + "rand_core", ] [[package]] @@ -901,6 +912,32 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "curve25519-dalek" +version = "5.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335f1947f241137a14106b6f5acc5918a5ede29c9d71d3f2cb1678d5075d9fc3" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dart-sys" version = "4.1.5" @@ -1314,6 +1351,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "flate2" version = "1.1.9" @@ -1773,6 +1816,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ + "ctutils", "typenum", "zeroize", ] @@ -2005,7 +2049,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.1", + "rand_core", ] [[package]] @@ -2207,12 +2261,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ml-kem" +version = "0.3.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04437cb1a66c0b78740927b76cc61f218344b9f6ef3dd430e283274a718ef0e9" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "rand_core", + "sha3", + "zeroize", +] + [[package]] name = "module-lattice" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "164eb3faeaecbd14b0b2a917c1b4d0c035097a9c559b0bed85c2cdd032bc8faa" dependencies = [ + "ctutils", "hybrid-array", "num-traits", "zeroize", @@ -3488,6 +3557,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -4761,6 +4836,20 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "x-wing" +version = "0.1.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d0d5f4d1f26b9b9e7477af1d3bef960e1d1fb64edab7912fde472a8a8432e" +dependencies = [ + "kem", + "ml-kem", + "rand_core", + "sha3", + "x25519-dalek", + "zeroize", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -4793,6 +4882,17 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "x25519-dalek" +version = "3.0.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d5d6ff67acd3945b933e592bfa7143db4fcbb2f871754b6b9fbd7847fc5aea" +dependencies = [ + "curve25519-dalek", + "rand_core", + "zeroize", +] + [[package]] name = "xcursor" version = "0.3.10" From 3b828d5874747e0123f3de88e9b66a05bceb0ade Mon Sep 17 00:00:00 2001 From: Skipper Date: Thu, 16 Apr 2026 22:14:55 +0200 Subject: [PATCH 08/13] refactor(server::grpc::vault_gate): standard approach using / traits --- .../src/grpc/user_agent/vault_gate.rs | 201 ++---------------- .../src/grpc/user_agent/vault_gate/inbound.rs | 129 +++++++++++ .../grpc/user_agent/vault_gate/outbound.rs | 115 ++++++++++ 3 files changed, 257 insertions(+), 188 deletions(-) create mode 100644 server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs create mode 100644 server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs index 381fc45..37f3c5b 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs @@ -1,55 +1,16 @@ -use arbiter_proto::{ - proto::{ - shared::VaultState as ProtoVaultState, - user_agent::{ - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, - vault::{ - self as proto_vault, - bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, - request::Payload as VaultRequestPayload, - response::Payload as VaultResponsePayload, - unseal::{ - self as proto_unseal, UnsealResult as ProtoUnsealResult, - request::Payload as UnsealRequestPayload, - response::Payload as UnsealResponsePayload, - }, - }, - }, - }, - transport::{Bi, Error as TransportError, Receiver, Sender}, -}; +use arbiter_proto::transport::{Bi, Error as TransportError, Receiver, Sender}; use async_trait::async_trait; use tonic::Status; use tracing::warn; use super::auth::AuthTransportAdapter; use crate::{ - actors::vault::VaultState, - peers::user_agent::vault_gate::{ - self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, - }, + grpc::TryConvert, + peers::user_agent::vault_gate::{self as vault_gate}, }; -fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { - UserAgentResponsePayload::Vault(proto_vault::Response { - payload: Some(payload), - }) -} - -fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload { - wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response { - payload: Some(payload), - })) -} - -fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload { - wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response { - result: result.into(), - })) -} - -impl AuthTransportAdapter<'_> {} +mod inbound; +mod outbound; #[async_trait] impl Receiver for AuthTransportAdapter<'_> { @@ -79,87 +40,12 @@ impl Receiver for AuthTransportAdapter<'_> { return None; }; - let vault_req = match payload { - UserAgentRequestPayload::Vault(req) => req, - _ => { - let _ = self - .bi_mut() - .send(Err(Status::permission_denied( - "Only vault operations are permitted before unsealing", - ))) - .await; + match payload.try_convert() { + Ok(inbound) => return Some(inbound), + Err(status) => { + let _ = self.bi_mut().send(Err(status)).await; return None; } - }; - - let Some(vault_payload) = vault_req.payload else { - let _ = self - .bi_mut() - .send(Err(Status::invalid_argument( - "Missing vault request payload", - ))) - .await; - return None; - }; - - match vault_payload { - VaultRequestPayload::QueryState(_) => { - return Some(vault_gate::Inbound::HandleVaultState); - } - VaultRequestPayload::Unseal(req) => { - let Some(unseal_payload) = req.payload else { - let _ = self - .bi_mut() - .send(Err(Status::invalid_argument( - "Missing unseal request payload", - ))) - .await; - return None; - }; - match unseal_payload { - UnsealRequestPayload::Start(start) => { - let Ok(bytes) = <[u8; 32]>::try_from(start.client_pubkey) else { - let _ = self - .bi_mut() - .send(Err(Status::invalid_argument( - "Invalid X25519 public key", - ))) - .await; - return None; - }; - return Some(vault_gate::Inbound::HandleHandshake(HandleHandshake { - client_pubkey: x25519_dalek::PublicKey::from(bytes), - })); - } - UnsealRequestPayload::EncryptedKey(key) => { - return Some(vault_gate::Inbound::HandleUnsealEncryptedKey( - HandleUnsealEncryptedKey { - nonce: key.nonce, - ciphertext: key.ciphertext, - associated_data: key.associated_data, - }, - )); - } - } - } - VaultRequestPayload::Bootstrap(req) => { - let Some(encrypted_key) = req.encrypted_key else { - let _ = self - .bi_mut() - .send(Err(Status::invalid_argument( - "Missing bootstrap encrypted key", - ))) - .await; - return None; - }; - return Some(vault_gate::Inbound::HandleBootstrapEncryptedKey( - HandleBootstrapEncryptedKey { - nonce: encrypted_key.nonce, - ciphertext: encrypted_key.ciphertext, - associated_data: encrypted_key.associated_data, - }, - )); - } } } } @@ -182,71 +68,10 @@ impl Sender> for AuthTransportAd } }; - let payload = match outbound { - vault_gate::Outbound::HandleVaultState(result) => match result { - Ok(state) => { - let state = match state { - VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped, - VaultState::Sealed => ProtoVaultState::Sealed, - VaultState::Unsealed => ProtoVaultState::Unsealed, - }; - - wrap_vault_response(VaultResponsePayload::State(state.into())) - } - Err(err) => { - warn!(?err, "vault state query failed"); - return self - .bi_mut() - .send(Err(Status::internal("Failed to query vault state"))) - .await; - } - }, - vault_gate::Outbound::HandleHandshake(Ok(response)) => wrap_unseal_response( - UnsealResponsePayload::Start(proto_unseal::UnsealStartResponse { - server_pubkey: response.server_pubkey.as_bytes().to_vec(), - }), - ), - vault_gate::Outbound::HandleHandshake(Err(err)) => { - warn!(?err, "handshake failed"); - return self - .bi_mut() - .send(Err(Status::internal("Failed to start unseal flow"))) - .await; - } - vault_gate::Outbound::HandleUnsealEncryptedKey(result) => { - let proto_result = match result { - Ok(()) => ProtoUnsealResult::Success, - Err(vault_gate::Error::InvalidKey) => ProtoUnsealResult::InvalidKey, - Err(err) => { - warn!(?err, "unseal failed"); - return self - .bi_mut() - .send(Err(Status::internal("Failed to unseal vault"))) - .await; - } - }; - wrap_unseal_response(UnsealResponsePayload::Result(proto_result.into())) - } - vault_gate::Outbound::HandleBootstrapEncryptedKey(result) => { - let proto_result = match result { - Ok(()) => ProtoBootstrapResult::Success, - Err(vault_gate::Error::InvalidKey) => ProtoBootstrapResult::InvalidKey, - Err(vault_gate::Error::AlreadyBootstrapped) => { - ProtoBootstrapResult::AlreadyBootstrapped - } - Err(err) => { - warn!(?err, "bootstrap failed"); - return self - .bi_mut() - .send(Err(Status::internal("Failed to bootstrap vault"))) - .await; - } - }; - wrap_bootstrap_response(proto_result) - } - }; - - self.send_response_payload(payload).await + match outbound.try_convert() { + Ok(payload) => self.send_response_payload(payload).await, + Err(status) => self.bi_mut().send(Err(status)).await, + } } } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs new file mode 100644 index 0000000..68761ae --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs @@ -0,0 +1,129 @@ +use arbiter_proto::proto::user_agent::{ + user_agent_request::Payload as UserAgentRequestPayload, + vault::{ + self as proto_vault, + bootstrap::{self as proto_bootstrap}, + request::Payload as VaultRequestPayload, + unseal::{self as proto_unseal, request::Payload as UnsealRequestPayload}, + }, +}; +use tonic::Status; + +use crate::{ + grpc::{Convert, TryConvert}, + peers::user_agent::vault_gate::{ + self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, + }, +}; + +impl TryConvert for UserAgentRequestPayload { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + match self { + UserAgentRequestPayload::Vault(req) => req.try_convert(), + _ => Err(Status::permission_denied( + "Only vault operations are permitted before unsealing", + )), + } + } +} + +impl TryConvert for proto_vault::Request { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + self.payload + .ok_or_else(|| Status::invalid_argument("Missing vault request payload"))? + .try_convert() + } +} + +impl TryConvert for VaultRequestPayload { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + match self { + VaultRequestPayload::QueryState(_) => Ok(vault_gate::Inbound::HandleVaultState), + VaultRequestPayload::Unseal(req) => req.try_convert(), + VaultRequestPayload::Bootstrap(req) => req.try_convert(), + } + } +} + +impl TryConvert for proto_unseal::Request { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + self.payload + .ok_or_else(|| Status::invalid_argument("Missing unseal request payload"))? + .try_convert() + } +} + +impl TryConvert for UnsealRequestPayload { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + match self { + UnsealRequestPayload::Start(start) => start.try_convert(), + UnsealRequestPayload::EncryptedKey(key) => Ok(key.convert()), + } + } +} + +impl TryConvert for proto_unseal::UnsealStart { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + let bytes = <[u8; 32]>::try_from(self.client_pubkey) + .map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?; + Ok(vault_gate::Inbound::HandleHandshake(HandleHandshake { + client_pubkey: x25519_dalek::PublicKey::from(bytes), + })) + } +} + +impl Convert for proto_unseal::UnsealEncryptedKey { + type Output = vault_gate::Inbound; + + fn convert(self) -> vault_gate::Inbound { + vault_gate::Inbound::HandleUnsealEncryptedKey(HandleUnsealEncryptedKey { + nonce: self.nonce, + ciphertext: self.ciphertext, + associated_data: self.associated_data, + }) + } +} + +impl TryConvert for proto_bootstrap::Request { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + self.encrypted_key + .ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))? + .try_convert() + } +} + +impl TryConvert for proto_bootstrap::BootstrapEncryptedKey { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + Ok(vault_gate::Inbound::HandleBootstrapEncryptedKey( + HandleBootstrapEncryptedKey { + nonce: self.nonce, + ciphertext: self.ciphertext, + associated_data: self.associated_data, + }, + )) + } +} diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs new file mode 100644 index 0000000..c594b33 --- /dev/null +++ b/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs @@ -0,0 +1,115 @@ +use arbiter_proto::proto::{ + shared::VaultState as ProtoVaultState, + user_agent::{ + user_agent_response::Payload as UserAgentResponsePayload, + vault::{ + self as proto_vault, + bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, + response::Payload as VaultResponsePayload, + unseal::{ + self as proto_unseal, UnsealResult as ProtoUnsealResult, + response::Payload as UnsealResponsePayload, + }, + }, + }, +}; +use tonic::Status; +use tracing::warn; + +use crate::{ + actors::vault::VaultState, + grpc::{Convert, TryConvert}, + peers::user_agent::vault_gate::{self as vault_gate}, +}; + +fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { + UserAgentResponsePayload::Vault(proto_vault::Response { + payload: Some(payload), + }) +} + +fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload { + wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response { + payload: Some(payload), + })) +} + +fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload { + wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response { + result: result.into(), + })) +} + +impl Convert for VaultState { + type Output = UserAgentResponsePayload; + + fn convert(self) -> UserAgentResponsePayload { + let proto_state = match self { + VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped, + VaultState::Sealed => ProtoVaultState::Sealed, + VaultState::Unsealed => ProtoVaultState::Unsealed, + }; + wrap_vault_response(VaultResponsePayload::State(proto_state.into())) + } +} + +impl Convert for vault_gate::HandshakeResponse { + type Output = UserAgentResponsePayload; + + fn convert(self) -> UserAgentResponsePayload { + wrap_unseal_response(UnsealResponsePayload::Start( + proto_unseal::UnsealStartResponse { + server_pubkey: self.server_pubkey.as_bytes().to_vec(), + }, + )) + } +} + +impl TryConvert for vault_gate::Outbound { + type Output = UserAgentResponsePayload; + type Error = Status; + + fn try_convert(self) -> Result { + match self { + vault_gate::Outbound::HandleVaultState(result) => result + .map_err(|err| { + warn!(?err, "vault state query failed"); + Status::internal("Failed to query vault state") + }) + .map(VaultState::convert), + vault_gate::Outbound::HandleHandshake(result) => result + .map_err(|err| { + warn!(?err, "handshake failed"); + Status::internal("Failed to start unseal flow") + }) + .map(vault_gate::HandshakeResponse::convert), + vault_gate::Outbound::HandleUnsealEncryptedKey(result) => { + let proto_result = match result { + Ok(()) => ProtoUnsealResult::Success, + Err(vault_gate::Error::InvalidKey) => ProtoUnsealResult::InvalidKey, + Err(err) => { + warn!(?err, "unseal failed"); + return Err(Status::internal("Failed to unseal vault")); + } + }; + Ok(wrap_unseal_response(UnsealResponsePayload::Result( + proto_result.into(), + ))) + } + vault_gate::Outbound::HandleBootstrapEncryptedKey(result) => { + let proto_result = match result { + Ok(()) => ProtoBootstrapResult::Success, + Err(vault_gate::Error::InvalidKey) => ProtoBootstrapResult::InvalidKey, + Err(vault_gate::Error::AlreadyBootstrapped) => { + ProtoBootstrapResult::AlreadyBootstrapped + } + Err(err) => { + warn!(?err, "bootstrap failed"); + return Err(Status::internal("Failed to bootstrap vault")); + } + }; + Ok(wrap_bootstrap_response(proto_result)) + } + } + } +} From 51e6571d80413023200e54d44b5511808cef633d Mon Sep 17 00:00:00 2001 From: Skipper Date: Thu, 16 Apr 2026 23:48:47 +0200 Subject: [PATCH 09/13] refactor(server): now keeps track of useragents, instead of --- .../src/actors/flow_coordinator/mod.rs | 48 +++++----- .../crates/arbiter-server/src/actors/mod.rs | 13 ++- .../src/actors/useragent_registry.rs | 89 ++++++++++--------- .../src/peers/user_agent/session/mod.rs | 11 +-- 4 files changed, 85 insertions(+), 76 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs index 51a9bac..20fee53 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs @@ -10,19 +10,27 @@ use kameo::{ use tracing::info; use crate::{ - actors::flow_coordinator::client_connect_approval::ClientApprovalController, - peers::{ - client::{ClientProfile, session::ClientSession}, - user_agent::UserAgentSession, + actors::{ + flow_coordinator::client_connect_approval::ClientApprovalController, + useragent_registry::{GetConnected, UserAgentRegistry}, }, + peers::client::{ClientProfile, session::ClientSession}, }; pub mod client_connect_approval; -#[derive(Default)] pub struct FlowCoordinator { - pub user_agents: HashMap>, pub clients: HashMap>, + useragent_registry: ActorRef, +} + +impl FlowCoordinator { + pub fn new(useragent_registry: ActorRef) -> Self { + Self { + clients: HashMap::default(), + useragent_registry, + } + } } impl Actor for FlowCoordinator { @@ -40,13 +48,7 @@ impl Actor for FlowCoordinator { id: ActorId, _: ActorStopReason, ) -> Result, Self::Error> { - if self.user_agents.remove(&id).is_some() { - info!( - ?id, - actor = "FlowCoordinator", - event = "useragent.disconnected" - ); - } else if self.clients.remove(&id).is_some() { + if self.clients.remove(&id).is_some() { info!( ?id, actor = "FlowCoordinator", @@ -71,17 +73,6 @@ pub enum ApprovalError { #[messages] impl FlowCoordinator { - #[message(ctx)] - pub async fn register_user_agent( - &mut self, - actor: ActorRef, - ctx: &mut Context, - ) { - info!(id = %actor.id(), actor = "FlowCoordinator", event = "useragent.connected"); - ctx.actor_ref().link(&actor).await; - self.user_agents.insert(actor.id(), actor); - } - #[message(ctx)] pub async fn register_client( &mut self, @@ -103,7 +94,14 @@ impl FlowCoordinator { unreachable!("Expected `request_client_approval` to have callback channel"); }; - let refs: Vec<_> = self.user_agents.values().cloned().collect(); + let refs = match self.useragent_registry.ask(GetConnected).await { + Ok(refs) => refs, + Err(_) => { + reply_sender.send(Err(ApprovalError::NoUserAgentsConnected)); + return reply; + } + }; + if refs.is_empty() { reply_sender.send(Err(ApprovalError::NoUserAgentsConnected)); return reply; diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index adb51f7..6b01dd8 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -4,7 +4,11 @@ use thiserror::Error; use crate::{ actors::{ - bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, vault::Vault, + bootstrap::Bootstrapper, + evm::EvmActor, + flow_coordinator::FlowCoordinator, + useragent_registry::UserAgentRegistry, + vault::Vault, }, db, }; @@ -30,6 +34,7 @@ pub struct GlobalActors { pub vault: ActorRef, pub bootstrapper: ActorRef, pub flow_coordinator: ActorRef, + pub useragent_registry: ActorRef, pub evm: ActorRef, pub events: ActorRef, } @@ -42,11 +47,15 @@ impl GlobalActors { pub async fn spawn(db: db::DatabasePool) -> Result { let message_bus = Self::spawn_message_bus(); let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?); + let useragent_registry = UserAgentRegistry::spawn(UserAgentRegistry::default()); Ok(Self { bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?), evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)), vault: key_holder, - flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::default()), + flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::new( + useragent_registry.clone(), + )), + useragent_registry, events: message_bus, }) } diff --git a/server/crates/arbiter-server/src/actors/useragent_registry.rs b/server/crates/arbiter-server/src/actors/useragent_registry.rs index 3c80735..74e616c 100644 --- a/server/crates/arbiter-server/src/actors/useragent_registry.rs +++ b/server/crates/arbiter-server/src/actors/useragent_registry.rs @@ -1,57 +1,58 @@ -use alloy::primitives::map::HashMap; -use arbiter_crypto::authn; -use kameo::{error::Infallible, prelude::*}; +use std::{collections::HashMap, ops::ControlFlow}; -use crate::{db::DatabasePool, peers::user_agent::{Credentials, UserAgentSession}}; +use kameo::{ + Actor, + actor::{ActorId, ActorRef}, + error::Infallible, + messages, + prelude::{ActorStopReason, Context, WeakActorRef}, +}; +use tracing::info; -use super::vault::{Vault, events as vault_events}; - -pub struct Args { - pub vault: ActorRef, - pub pool: DatabasePool, -} +use crate::peers::user_agent::UserAgentSession; +#[derive(Default)] pub struct UserAgentRegistry { - vault: ActorRef, - pool: DatabasePool, - connected: HashMap>, + connected: HashMap>, } -impl Message for UserAgentRegistry { - type Reply = (); - - async fn handle( - &mut self, - msg: vault_events::Bootstrapped, - ctx: &mut Context, - ) -> Self::Reply { - todo!() - } -} - -impl Message for UserAgentRegistry { - type Reply = (); - - async fn handle( - &mut self, - msg: vault_events::Unsealed, - ctx: &mut Context, - ) -> Self::Reply { - todo!() - } -} impl Actor for UserAgentRegistry { - type Args = Args; + type Args = Self; type Error = Infallible; - async fn on_start(args: Self::Args, actor_ref: ActorRef) -> Result { - Ok(Self { - vault: args.vault, - pool: args.pool, - connected: HashMap::default(), - }) + async fn on_start(args: Self::Args, _: ActorRef) -> Result { + Ok(args) } - + async fn on_link_died( + &mut self, + _: WeakActorRef, + id: ActorId, + _: ActorStopReason, + ) -> Result, Self::Error> { + if self.connected.remove(&id).is_some() { + info!(?id, actor = "UserAgentRegistry", event = "useragent.disconnected"); + } + Ok(ControlFlow::Continue(())) + } +} + +#[messages] +impl UserAgentRegistry { + #[message(ctx)] + pub async fn connect_useragent( + &mut self, + actor: ActorRef, + ctx: &mut Context, + ) { + info!(id = %actor.id(), actor = "UserAgentRegistry", event = "useragent.connected"); + ctx.actor_ref().link(&actor).await; + self.connected.insert(actor.id(), actor); + } + + #[message] + pub fn get_connected(&self) -> Vec> { + self.connected.values().cloned().collect() + } } diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs index 55cd9e5..7356d04 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs @@ -12,7 +12,8 @@ use tracing::error; use crate::{ actors::{ - flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, + flow_coordinator::client_connect_approval::ClientApprovalController, + useragent_registry::ConnectUseragent, vault::events, }, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::{AuthCredentials, Credentials}} }; @@ -123,17 +124,17 @@ impl Actor for UserAgentSession { ) -> Result { args.props .actors - .flow_coordinator - .ask(RegisterUserAgent { + .useragent_registry + .ask(ConnectUseragent { actor: this.clone(), }) .await .map_err(|err| { error!( ?err, - "Failed to register user agent connection with flow coordinator" + "Failed to register user agent connection with user agent registry" ); - Error::internal("Failed to register user agent connection with flow coordinator") + Error::internal("Failed to register user agent connection with user agent registry") })?; Ok(args) } From 0e09afda5d2ac29706c8bda1728ea0ea837f897f Mon Sep 17 00:00:00 2001 From: Skipper Date: Fri, 17 Apr 2026 16:14:45 +0200 Subject: [PATCH 10/13] refactor(server::{useragent::auth, client::auth}): use random based + timestamp nonce instead of monotonic counter in database --- protobufs/client/auth.proto | 4 +- protobufs/user_agent/auth.proto | 3 +- server/Cargo.lock | 3 +- server/crates/arbiter-client/Cargo.toml | 1 + server/crates/arbiter-client/src/auth.rs | 15 +- server/crates/arbiter-crypto/Cargo.toml | 4 +- server/crates/arbiter-crypto/src/authn/v1.rs | 82 +++-- .../2026-02-14-171124-0000_init/up.sql | 2 - server/crates/arbiter-server/src/db/models.rs | 2 - server/crates/arbiter-server/src/db/schema.rs | 2 - .../arbiter-server/src/grpc/client/auth.rs | 10 +- .../src/grpc/user_agent/auth.rs | 15 +- .../arbiter-server/src/peers/client/auth.rs | 87 +---- .../arbiter-server/src/peers/client/mod.rs | 2 - .../src/peers/user_agent/auth/mod.rs | 19 +- .../src/peers/user_agent/auth/state.rs | 311 +++++------------- .../src/peers/user_agent/mod.rs | 116 ++++--- .../src/peers/user_agent/session/handlers.rs | 15 +- .../src/peers/user_agent/session/mod.rs | 9 +- .../src/peers/user_agent/vault_gate/mod.rs | 48 +-- .../src/peers/user_agent/vault_gate/state.rs | 3 +- .../arbiter-server/tests/client/auth.rs | 10 +- .../arbiter-server/tests/user_agent/auth.rs | 6 +- .../arbiter-server/tests/user_agent/unseal.rs | 17 +- 24 files changed, 320 insertions(+), 466 deletions(-) diff --git a/protobufs/client/auth.proto b/protobufs/client/auth.proto index f3d7d2d..382cd23 100644 --- a/protobufs/client/auth.proto +++ b/protobufs/client/auth.proto @@ -10,8 +10,8 @@ message AuthChallengeRequest { } message AuthChallenge { - bytes pubkey = 1; - int32 nonce = 2; + uint64 timestamp_nanos = 1; + bytes random = 2; } message AuthChallengeSolution { diff --git a/protobufs/user_agent/auth.proto b/protobufs/user_agent/auth.proto index 989e339..291084e 100644 --- a/protobufs/user_agent/auth.proto +++ b/protobufs/user_agent/auth.proto @@ -8,7 +8,8 @@ message AuthChallengeRequest { } message AuthChallenge { - int32 nonce = 1; + uint64 timestamp_nanos = 1; + bytes random = 2; } message AuthChallengeSolution { diff --git a/server/Cargo.lock b/server/Cargo.lock index 7ec7471..4b908ba 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -683,6 +683,7 @@ dependencies = [ "arbiter-crypto", "arbiter-proto", "async-trait", + "chrono", "http", "rand 0.10.1", "rustls-webpki", @@ -696,7 +697,7 @@ dependencies = [ name = "arbiter-crypto" version = "0.1.0" dependencies = [ - "base64", + "chrono", "memsafe", "ml-dsa", "rand 0.10.1", diff --git a/server/crates/arbiter-client/Cargo.toml b/server/crates/arbiter-client/Cargo.toml index 30f5d14..b2d3810 100644 --- a/server/crates/arbiter-client/Cargo.toml +++ b/server/crates/arbiter-client/Cargo.toml @@ -24,3 +24,4 @@ http = "1.4.0" rustls-webpki = { version = "0.103.10", features = ["aws-lc-rs"] } async-trait.workspace = true rand.workspace = true +chrono.workspace = true diff --git a/server/crates/arbiter-client/src/auth.rs b/server/crates/arbiter-client/src/auth.rs index e6068e5..d9d4dbb 100644 --- a/server/crates/arbiter-client/src/auth.rs +++ b/server/crates/arbiter-client/src/auth.rs @@ -1,4 +1,4 @@ -use arbiter_crypto::authn::{CLIENT_CONTEXT, SigningKey, format_challenge}; +use arbiter_crypto::authn::{self, CLIENT_CONTEXT, SigningKey}; use arbiter_proto::{ ClientMetadata, proto::{ @@ -15,6 +15,7 @@ use arbiter_proto::{ shared::ClientInfo as ProtoClientInfo, }, }; +use chrono::DateTime; use crate::{ storage::StorageError, @@ -23,6 +24,8 @@ use crate::{ #[derive(Debug, thiserror::Error)] pub enum AuthError { + #[error("Server sent invalid auth challenge")] + InvalidChallenge, #[error("Auth challenge was not returned by server")] MissingAuthChallenge, @@ -98,7 +101,15 @@ async fn send_auth_challenge_solution( key: &SigningKey, challenge: AuthChallenge, ) -> std::result::Result<(), AuthError> { - let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey); + let timestamp = DateTime::from_timestamp_nanos(challenge.timestamp_nanos as i64); + let challenge = authn::AuthChallenge { + nonce: *challenge + .random + .as_array() + .ok_or(AuthError::InvalidChallenge)?, + timestamp, + }; + let challenge_payload: Vec = challenge.format(); let signature = key .sign_message(&challenge_payload, CLIENT_CONTEXT) .map_err(|_| AuthError::UnexpectedAuthResponse)? diff --git a/server/crates/arbiter-crypto/Cargo.toml b/server/crates/arbiter-crypto/Cargo.toml index 238b3db..4f4c178 100644 --- a/server/crates/arbiter-crypto/Cargo.toml +++ b/server/crates/arbiter-crypto/Cargo.toml @@ -6,14 +6,14 @@ edition = "2024" [dependencies] ml-dsa = {workspace = true, optional = true } rand = {workspace = true, optional = true} -base64 = {workspace = true, optional = true } memsafe = {version = "0.4.0", optional = true} x-wing = { version = "0.1.0-rc.0", features = ["zeroize"] } +chrono.workspace = true [lints] workspace = true [features] default = ["authn", "safecell"] -authn = ["dep:ml-dsa", "dep:rand", "dep:base64"] +authn = ["dep:ml-dsa", "dep:rand"] safecell = ["dep:memsafe"] diff --git a/server/crates/arbiter-crypto/src/authn/v1.rs b/server/crates/arbiter-crypto/src/authn/v1.rs index ff65104..f192e76 100644 --- a/server/crates/arbiter-crypto/src/authn/v1.rs +++ b/server/crates/arbiter-crypto/src/authn/v1.rs @@ -1,17 +1,48 @@ use std::hash::Hash; -use base64::{Engine as _, prelude::BASE64_STANDARD}; +use chrono::{DateTime, Utc}; use ml_dsa::{ EncodedVerifyingKey, Error, KeyGen, MlDsa87, Seed, Signature as MlDsaSignature, SigningKey as MlDsaSigningKey, VerifyingKey as MlDsaVerifyingKey, signature::Keypair as _, }; +use rand::RngExt; pub static CLIENT_CONTEXT: &[u8] = b"arbiter_client"; pub static USERAGENT_CONTEXT: &[u8] = b"arbiter_user_agent"; -pub fn format_challenge(nonce: i32, pubkey: &[u8]) -> Vec { - let concat_form = format!("{}:{}", nonce, BASE64_STANDARD.encode(pubkey)); - concat_form.into_bytes() +const NONCE_SIZE: usize = 32; + +#[derive(Debug, Clone)] +pub struct AuthChallenge { + pub nonce: [u8; NONCE_SIZE], + pub timestamp: DateTime, +} + +impl AuthChallenge { + pub fn generate(rng: &mut impl rand::CryptoRng) -> Self { + let timestamp = Utc::now(); + let nonce = { + let mut array = [0; NONCE_SIZE]; + rng.fill(&mut array); + array + }; + + Self { nonce, timestamp } + } + + pub fn format(&self) -> Vec { + { + let mut buffer = Vec::from(self.nonce); + + let stamp = self + .timestamp + .timestamp_nanos_opt() + .expect("We would be long dead by the time this triggers :)"); + buffer.extend_from_slice(stamp.to_be_bytes().as_slice()); + + buffer + } + } } pub type KeyParams = MlDsa87; @@ -36,12 +67,10 @@ impl PublicKey { self.0.encode().to_vec() } - pub fn verify(&self, nonce: i32, context: &[u8], signature: &Signature) -> bool { - self.0.verify_with_context( - &format_challenge(nonce, &self.to_bytes()), - context, - &signature.0, - ) + pub fn verify(&self, challenge: &AuthChallenge, context: &[u8], signature: &Signature) -> bool { + let challenge = challenge.format(); + self.0 + .verify_with_context(&challenge, context, &signature.0) } } @@ -75,11 +104,14 @@ impl SigningKey { .map(Into::into) } - pub fn sign_challenge(&self, nonce: i32, context: &[u8]) -> Result { - self.sign_message( - &format_challenge(nonce, &self.public_key().to_bytes()), - context, - ) + pub fn sign_challenge( + &self, + challenge: &AuthChallenge, + context: &[u8], + ) -> Result { + let challenge = challenge.format(); + + self.sign_message(&challenge, context) } } @@ -140,6 +172,8 @@ impl TryFrom<&'_ [u8]> for Signature { mod tests { use ml_dsa::{KeyGen, MlDsa87, signature::Keypair as _}; + use crate::authn::AuthChallenge; + use super::{CLIENT_CONTEXT, PublicKey, Signature, SigningKey, USERAGENT_CONTEXT}; #[test] @@ -169,13 +203,13 @@ mod tests { fn challenge_verification_uses_context_and_canonical_key_bytes() { let key = SigningKey::generate(); let public_key = key.public_key(); - let nonce = 17; + let challenge = AuthChallenge::generate(&mut rand::rng()); let signature = key - .sign_challenge(nonce, CLIENT_CONTEXT) + .sign_challenge(&challenge, CLIENT_CONTEXT) .expect("signature should be created"); - assert!(public_key.verify(nonce, CLIENT_CONTEXT, &signature)); - assert!(!public_key.verify(nonce, USERAGENT_CONTEXT, &signature)); + assert!(public_key.verify(&challenge, CLIENT_CONTEXT, &signature)); + assert!(!public_key.verify(&challenge, USERAGENT_CONTEXT, &signature)); } #[test] @@ -185,10 +219,16 @@ mod tests { assert_eq!(restored.public_key(), original.public_key()); + let challenge = AuthChallenge::generate(&mut rand::rng()); + let signature = restored - .sign_challenge(9, CLIENT_CONTEXT) + .sign_challenge(&challenge, CLIENT_CONTEXT) .expect("signature should be created"); - assert!(restored.public_key().verify(9, CLIENT_CONTEXT, &signature)); + assert!( + restored + .public_key() + .verify(&challenge, CLIENT_CONTEXT, &signature) + ); } } 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 bb33278..dfc64d3 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 @@ -45,9 +45,7 @@ insert into arbiter_settings (id) values (1) on conflict do nothing; create table if not exists useragent_client ( id integer not null primary key, - nonce integer not null default(1), -- used for auth challenge public_key blob not null, - key_type integer not null default(1), created_at integer not null default(unixepoch ('now')), updated_at integer not null default(unixepoch ('now')) ) STRICT; diff --git a/server/crates/arbiter-server/src/db/models.rs b/server/crates/arbiter-server/src/db/models.rs index dba14dc..00d16d8 100644 --- a/server/crates/arbiter-server/src/db/models.rs +++ b/server/crates/arbiter-server/src/db/models.rs @@ -195,7 +195,6 @@ pub struct ProgramClientMetadataHistory { #[diesel(table_name = schema::program_client, check_for_backend(Sqlite))] pub struct ProgramClient { pub id: i32, - pub nonce: i32, pub public_key: Vec, pub metadata_id: i32, pub created_at: SqliteTimestamp, @@ -206,7 +205,6 @@ pub struct ProgramClient { #[diesel(table_name = schema::useragent_client, check_for_backend(Sqlite))] pub struct UseragentClient { pub id: i32, - pub nonce: i32, pub public_key: Vec, pub created_at: SqliteTimestamp, pub updated_at: SqliteTimestamp, diff --git a/server/crates/arbiter-server/src/db/schema.rs b/server/crates/arbiter-server/src/db/schema.rs index c9b980c..f02c036 100644 --- a/server/crates/arbiter-server/src/db/schema.rs +++ b/server/crates/arbiter-server/src/db/schema.rs @@ -155,7 +155,6 @@ diesel::table! { diesel::table! { program_client (id) { id -> Integer, - nonce -> Integer, public_key -> Binary, metadata_id -> Integer, created_at -> Integer, @@ -189,7 +188,6 @@ diesel::table! { diesel::table! { useragent_client (id) { id -> Integer, - nonce -> Integer, public_key -> Binary, key_type -> Integer, created_at -> Integer, diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 000a9db..fc16dbf 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -44,10 +44,14 @@ impl<'a> AuthTransportAdapter<'a> { fn response_to_proto(response: auth::Outbound) -> AuthResponsePayload { match response { - auth::Outbound::AuthChallenge { pubkey, nonce } => { + auth::Outbound::AuthChallenge { challenge } => { AuthResponsePayload::Challenge(ProtoAuthChallenge { - pubkey: pubkey.to_bytes(), - nonce, + timestamp_nanos: challenge + .timestamp + .timestamp_nanos_opt() + .expect("timestamp within range") + as u64, + random: challenge.nonce.to_vec(), }) } auth::Outbound::AuthSuccess => { diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs index 53ee57d..f9e625e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent/auth.rs @@ -19,7 +19,7 @@ use tracing::warn; use crate::{ grpc::request_tracker::RequestTracker, - peers::user_agent::{AuthCredentials, UserAgentConnection, auth}, + peers::user_agent::{Credentials, UserAgentConnection, auth}, }; pub struct AuthTransportAdapter<'a> { @@ -77,8 +77,15 @@ impl Sender> for AuthTransportAdapter<'_> { ) -> Result<(), TransportError> { use auth::{Error, Outbound}; let payload = match item { - Ok(Outbound::AuthChallenge { nonce }) => { - AuthResponsePayload::Challenge(ProtoAuthChallenge { nonce }) + Ok(Outbound::AuthChallenge { challenge }) => { + AuthResponsePayload::Challenge(ProtoAuthChallenge { + timestamp_nanos: challenge + .timestamp + .timestamp_nanos_opt() + .expect("timestamp within range") + as u64, + random: challenge.nonce.to_vec(), + }) } Ok(Outbound::AuthSuccess) => { AuthResponsePayload::Result(ProtoAuthResult::Success.into()) @@ -183,7 +190,7 @@ pub async fn start( conn: &mut UserAgentConnection, bi: &mut GrpcBi, request_tracker: &mut RequestTracker, -) -> Result { +) -> Result { let mut transport = AuthTransportAdapter::new(bi, request_tracker); auth::authenticate(conn, &mut transport).await } diff --git a/server/crates/arbiter-server/src/peers/client/auth.rs b/server/crates/arbiter-server/src/peers/client/auth.rs index 2152ace..98f6bd4 100644 --- a/server/crates/arbiter-server/src/peers/client/auth.rs +++ b/server/crates/arbiter-server/src/peers/client/auth.rs @@ -1,4 +1,4 @@ -use arbiter_crypto::authn::{self, CLIENT_CONTEXT}; +use arbiter_crypto::authn::{self, AuthChallenge, CLIENT_CONTEXT}; use arbiter_proto::{ ClientMetadata, transport::{Bi, expect_message}, @@ -74,19 +74,14 @@ pub enum Inbound { #[derive(Debug, Clone)] pub enum Outbound { - AuthChallenge { - pubkey: authn::PublicKey, - nonce: i32, - }, + AuthChallenge { challenge: AuthChallenge }, AuthSuccess, } -/// Returns the current nonce and client ID for a registered client. -/// Returns `None` if the pubkey is not registered. -async fn get_current_nonce_and_id( +async fn get_client_id( db: &db::DatabasePool, pubkey: &authn::PublicKey, -) -> Result, Error> { +) -> Result, Error> { let pubkey_bytes = pubkey.to_bytes(); let mut conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); @@ -94,8 +89,8 @@ async fn get_current_nonce_and_id( })?; program_client::table .filter(program_client::public_key.eq(&pubkey_bytes)) - .select((program_client::id, program_client::nonce)) - .first::<(i32, i32)>(&mut conn) + .select(program_client::id) + .first::(&mut conn) .await .optional() .map_err(|e| { @@ -114,7 +109,7 @@ async fn verify_integrity( Error::DatabasePoolUnavailable })?; - let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?.ok_or_else(|| { + let id = get_client_id(db, pubkey).await?.ok_or_else(|| { error!("Client not found during integrity verification"); Error::DatabaseOperationFailed })?; @@ -124,7 +119,6 @@ async fn verify_integrity( vault, &ClientCredentials { pubkey: pubkey.clone(), - nonce, }, id, ) @@ -142,53 +136,6 @@ async fn verify_integrity( Ok(()) } -/// Atomically increments the nonce and re-signs the integrity envelope. -/// Returns the new nonce, which is used as the challenge nonce. -async fn create_nonce( - db: &db::DatabasePool, - vault: &ActorRef, - pubkey: &authn::PublicKey, -) -> Result { - let pubkey_bytes = pubkey.to_bytes(); - let pubkey = pubkey.clone(); - - let mut conn = db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::DatabasePoolUnavailable - })?; - - conn.exclusive_transaction(|conn| { - let vault = vault.clone(); - let pubkey = pubkey.clone(); - Box::pin(async move { - let (id, new_nonce): (i32, i32) = update(program_client::table) - .filter(program_client::public_key.eq(&pubkey_bytes)) - .set(program_client::nonce.eq(program_client::nonce + 1)) - .returning((program_client::id, program_client::nonce)) - .get_result(conn) - .await?; - - integrity::sign_entity( - conn, - &vault, - &ClientCredentials { - pubkey: pubkey.clone(), - nonce: new_nonce, - }, - id, - ) - .await - .map_err(|e| { - error!(?e, "Integrity sign failed after nonce update"); - Error::DatabaseOperationFailed - })?; - - Ok(new_nonce) - }) - }) - .await -} - async fn approve_new_client(actors: &GlobalActors, profile: ClientProfile) -> Result<(), Error> { let result = actors .flow_coordinator @@ -228,8 +175,6 @@ async fn insert_client( let vault = vault.clone(); let pubkey = pubkey.clone(); Box::pin(async move { - const NONCE_START: i32 = 1; - let metadata_id = insert_into(client_metadata::table) .values(( client_metadata::name.eq(&metadata.name), @@ -244,7 +189,6 @@ async fn insert_client( .values(( program_client::public_key.eq(pubkey.to_bytes()), program_client::metadata_id.eq(metadata_id), - program_client::nonce.eq(NONCE_START), )) .on_conflict_do_nothing() .returning(program_client::id) @@ -256,7 +200,6 @@ async fn insert_client( &vault, &ClientCredentials { pubkey: pubkey.clone(), - nonce: NONCE_START, }, client_id, ) @@ -346,15 +289,14 @@ async fn sync_client_metadata( async fn challenge_client( transport: &mut T, pubkey: authn::PublicKey, - nonce: i32, + challenge: AuthChallenge, ) -> Result<(), Error> where T: Bi> + ?Sized, { transport .send(Ok(Outbound::AuthChallenge { - pubkey: pubkey.clone(), - nonce, + challenge: challenge.clone(), })) .await .map_err(|e| { @@ -372,7 +314,7 @@ where Error::Transport })?; - if !pubkey.verify(nonce, CLIENT_CONTEXT, &signature) { + if !pubkey.verify(&challenge, CLIENT_CONTEXT, &signature) { error!("Challenge solution verification failed"); return Err(Error::InvalidChallengeSolution); } @@ -388,8 +330,8 @@ where return Err(Error::Transport); }; - let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? { - Some((id, _)) => { + let client_id = match get_client_id(&props.db, &pubkey).await? { + Some(id) => { verify_integrity(&props.db, &props.actors.vault, &pubkey).await?; id } @@ -407,8 +349,9 @@ where }; sync_client_metadata(&props.db, client_id, &metadata).await?; - let challenge_nonce = create_nonce(&props.db, &props.actors.vault, &pubkey).await?; - challenge_client(transport, pubkey, challenge_nonce).await?; + + let challenge = AuthChallenge::generate(&mut rand::rng()); + challenge_client(transport, pubkey, challenge).await?; transport .send(Ok(Outbound::AuthSuccess)) diff --git a/server/crates/arbiter-server/src/peers/client/mod.rs b/server/crates/arbiter-server/src/peers/client/mod.rs index 79e79ff..f705752 100644 --- a/server/crates/arbiter-server/src/peers/client/mod.rs +++ b/server/crates/arbiter-server/src/peers/client/mod.rs @@ -18,7 +18,6 @@ pub struct ClientProfile { pub struct ClientCredentials { pub pubkey: authn::PublicKey, - pub nonce: i32, } impl Integrable for ClientCredentials { @@ -28,7 +27,6 @@ impl Integrable for ClientCredentials { impl Hashable for ClientCredentials { fn hash(&self, hasher: &mut H) { hasher.update(self.pubkey.to_bytes()); - self.nonce.hash(hasher); } } diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs index 97a510a..8332b5b 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs @@ -1,11 +1,12 @@ -use arbiter_crypto::authn; +use arbiter_crypto::authn::{self, AuthChallenge}; use arbiter_proto::transport::Bi; use tracing::error; mod state; use state::*; -use super::{AuthCredentials, UserAgentConnection}; +use super::Credentials; +use super::UserAgentConnection; #[derive(Debug, Clone)] pub enum Inbound { @@ -44,7 +45,7 @@ impl From for Error { #[derive(Debug, Clone)] pub enum Outbound { - AuthChallenge { nonce: i32 }, + AuthChallenge { challenge: AuthChallenge }, AuthSuccess, } @@ -52,12 +53,11 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { match payload { Inbound::AuthChallengeRequest { pubkey, - bootstrap_token: None, - } => AuthEvents::AuthRequest(ChallengeRequest { pubkey }), - Inbound::AuthChallengeRequest { + bootstrap_token, + } => AuthEvents::AuthRequest(ChallengeRequest { pubkey, - bootstrap_token: Some(token), - } => AuthEvents::BootstrapAuthRequest(BootstrapAuthRequest { pubkey, token }), + bootstrap_token, + }), Inbound::AuthChallengeSolution { signature } => { AuthEvents::ReceivedSolution(ChallengeSolution { solution: signature, @@ -69,14 +69,13 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { pub async fn authenticate( props: &mut UserAgentConnection, transport: &mut T, -) -> Result +) -> Result where T: Bi> + Send + ?Sized, { let mut state = AuthStateMachine::new(AuthContext::new(props, transport)); loop { - // `state` holds a mutable reference to `props` so we can't access it directly here let Some(payload) = state.context_mut().transport.recv().await else { return Err(Error::Transport); }; diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index bb48291..04f8b2e 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -1,32 +1,26 @@ -use super::super::{AuthCredentials, Credentials, UserAgentConnection}; -use arbiter_crypto::authn::{self, USERAGENT_CONTEXT}; +use super::super::{Credentials, UserAgentConnection}; +use arbiter_crypto::authn::{self, AuthChallenge, USERAGENT_CONTEXT}; use arbiter_proto::transport::Bi; -use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, sqlite::Sqlite, update}; -use diesel_async::{AsyncConnection, RunQueryDsl}; -use kameo::actor::ActorRef; +use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl}; +use diesel_async::RunQueryDsl; use tracing::error; use super::Error; -use crate::peers::user_agent::auth::Outbound; use crate::{ - actors::{bootstrap::ConsumeToken, vault::Vault}, - crypto::integrity, + actors::bootstrap::ConsumeToken, db::{DatabasePool, schema::useragent_client}, + peers::user_agent::auth::Outbound, }; pub struct ChallengeRequest { pub pubkey: authn::PublicKey, -} - -pub struct BootstrapAuthRequest { - pub pubkey: authn::PublicKey, - pub token: String, + pub bootstrap_token: Option, } pub struct ChallengeContext { - pub id: i32, - pub challenge_nonce: i32, - pub key: authn::PublicKey, + pub challenge: AuthChallenge, + pub pubkey: authn::PublicKey, + pub bootstrap_token: Option, } pub struct ChallengeSolution { @@ -38,119 +32,28 @@ smlang::statemachine!( custom_error: true, transitions: { *Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext), - Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthCredentials), - SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthCredentials), + SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(Credentials), } ); -const NONCE_START: i32 = 1; - -/// Returns the current nonce, ready to use for the challenge nonce. -async fn get_current_nonce_and_id( - db: &DatabasePool, - key: &authn::PublicKey, -) -> Result<(i32, i32), Error> { - let mut db_conn = db.get().await.map_err(|e| { +async fn get_client_id(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result, Error> { + let mut conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); Error::internal("Database unavailable") })?; - db_conn - .exclusive_transaction(|conn| { - Box::pin(async move { - useragent_client::table - .filter(useragent_client::public_key.eq(key.to_bytes())) - .select((useragent_client::id, useragent_client::nonce)) - .first::<(i32, i32)>(conn) - .await - }) - }) + + useragent_client::table + .filter(useragent_client::public_key.eq(pubkey.to_bytes())) + .select(useragent_client::id) + .first::(&mut conn) .await .optional() .map_err(|e| { error!(error = ?e, "Database error"); Error::internal("Database operation failed") - })? - .ok_or_else(|| { - error!(?key, "Public key not found in database"); - Error::UnregisteredPublicKey }) } -async fn verify_integrity( - db: &DatabasePool, - vault: &ActorRef, - pubkey: &authn::PublicKey, -) -> Result<(), Error> { - let mut db_conn = db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::internal("Database unavailable") - })?; - - let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?; - - let _result = integrity::verify_entity( - &mut db_conn, - vault, - &AuthCredentials { - creds: Credentials { - id, - pubkey: pubkey.clone(), - }, - new_nonce: nonce, - }, - id, - ) - .await - .map_err(|e| { - error!(?e, "Integrity verification failed"); - Error::internal("Integrity verification failed") - })?; - - Ok(()) -} - -async fn compute_current_nonce( - conn: &mut impl AsyncConnection, - pubkey: &authn::PublicKey, -) -> Result<(i32, i32), Error> { - update(useragent_client::table) - .filter(useragent_client::public_key.eq(pubkey.to_bytes())) - .set(useragent_client::nonce.eq(useragent_client::nonce + 1)) - .returning((useragent_client::id, useragent_client::nonce)) - .get_result(conn) - .await - .map_err(|e| { - error!(error = ?e, "Database error incrementing nonce"); - Error::internal("Database operation failed") - }) -} - -async fn resign_credentials( - conn: &mut impl AsyncConnection, - vault: &ActorRef, - id: i32, - pubkey: &authn::PublicKey, - new_nonce: i32, -) -> Result<(), Error> { - integrity::sign_entity( - conn, - vault, - &AuthCredentials { - creds: Credentials { - id, - pubkey: pubkey.clone(), - }, - new_nonce, - }, - id, - ) - .await - .map_err(|e| { - error!(?e, "Integrity signature update failed"); - Error::internal("Database error") - }) -} - async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result { let pubkey_bytes = pubkey.to_bytes(); let mut conn = db.get().await.map_err(|e| { @@ -159,10 +62,7 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result Result { - let is_signing = integrity::is_signing_available(&self.conn.actors.vault) - .await - .unwrap_or(false); - - if is_signing { - verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?; + // Verify pubkey is registered (unless bootstrapping) + if bootstrap_token.is_none() { + let id = get_client_id(&self.conn.db, &pubkey).await?; + if id.is_none() { + return Err(Error::UnregisteredPublicKey); + } } - let vault = self.conn.actors.vault.clone(); - let mut conn = self.conn.db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::internal("Database unavailable") - })?; - - let (id, nonce) = conn - .exclusive_transaction(|conn| { - let pubkey = pubkey.clone(); - let vault = vault.clone(); - Box::pin(async move { - let (id, new_nonce) = compute_current_nonce(conn, &pubkey).await?; - if is_signing { - resign_credentials(conn, &vault, id, &pubkey, new_nonce).await?; - } - Result::<_, Error>::Ok((id, new_nonce)) - }) - }) - .await?; + let challenge = AuthChallenge::generate(&mut rand::rng()); self.transport - .send(Ok(Outbound::AuthChallenge { nonce })) + .send(Ok(Outbound::AuthChallenge { + challenge: challenge.clone(), + })) .await .map_err(|e| { error!(?e, "Failed to send auth challenge"); @@ -232,98 +119,78 @@ where })?; Ok(ChallengeContext { - id, - challenge_nonce: nonce, - key: pubkey, + challenge, + pubkey, + bootstrap_token, }) } - #[allow(missing_docs)] - #[allow(clippy::result_unit_err)] - async fn verify_bootstrap_token( - &mut self, - BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest, - ) -> Result { - let token_ok: bool = self - .conn - .actors - .bootstrapper - .ask(ConsumeToken { - token: token.clone(), - }) - .await - .map_err(|e| { - error!(?e, "Failed to consume bootstrap token"); - Error::internal("Failed to consume bootstrap token") - })?; - - if !token_ok { - error!("Invalid bootstrap token provided"); - return Err(Error::InvalidBootstrapToken); - } - - match token_ok { - true => { - let id = register_key(&self.conn.db, &pubkey).await?; - self.transport - .send(Ok(Outbound::AuthSuccess)) - .await - .map_err(|_| Error::Transport)?; - Ok(AuthCredentials { - creds: Credentials { id, pubkey }, - new_nonce: NONCE_START, - }) - } - false => { - error!("Invalid bootstrap token provided"); - self.transport - .send(Err(Error::InvalidBootstrapToken)) - .await - .map_err(|_| Error::Transport)?; - Err(Error::InvalidBootstrapToken) - } - } - } - #[allow(missing_docs)] #[allow(clippy::unused_unit)] async fn verify_solution( &mut self, ChallengeContext { - id, - challenge_nonce, - key, + challenge, + pubkey, + bootstrap_token, }: &ChallengeContext, ChallengeSolution { solution }: ChallengeSolution, - ) -> Result { + ) -> Result { let signature = authn::Signature::try_from(solution.as_slice()).map_err(|_| { error!("Failed to decode signature in challenge solution"); Error::InvalidChallengeSolution })?; - let valid = key.verify(*challenge_nonce, USERAGENT_CONTEXT, &signature); + let valid = pubkey.verify(challenge, USERAGENT_CONTEXT, &signature); - match valid { - true => { - self.transport - .send(Ok(Outbound::AuthSuccess)) - .await - .map_err(|_| Error::Transport)?; - Ok(AuthCredentials { - creds: Credentials { - id: *id, - pubkey: key.clone(), - }, - new_nonce: *challenge_nonce, - }) - } - false => { - self.transport - .send(Err(Error::InvalidChallengeSolution)) - .await - .map_err(|_| Error::Transport)?; - Err(Error::InvalidChallengeSolution) - } + if !valid { + self.transport + .send(Err(Error::InvalidChallengeSolution)) + .await + .map_err(|_| Error::Transport)?; + return Err(Error::InvalidChallengeSolution); } + + // Resolve client id: bootstrap (consume token + register) or lookup + let id = match bootstrap_token { + Some(token) => { + let token_ok: bool = self + .conn + .actors + .bootstrapper + .ask(ConsumeToken { + token: token.clone(), + }) + .await + .map_err(|e| { + error!(?e, "Failed to consume bootstrap token"); + Error::internal("Failed to consume bootstrap token") + })?; + + if !token_ok { + error!("Invalid bootstrap token provided"); + self.transport + .send(Err(Error::InvalidBootstrapToken)) + .await + .map_err(|_| Error::Transport)?; + return Err(Error::InvalidBootstrapToken); + } + + register_key(&self.conn.db, pubkey).await? + } + None => get_client_id(&self.conn.db, pubkey) + .await? + .ok_or(Error::UnregisteredPublicKey)?, + }; + + self.transport + .send(Ok(Outbound::AuthSuccess)) + .await + .map_err(|_| Error::Transport)?; + + Ok(Credentials { + id, + pubkey: pubkey.clone(), + }) } } diff --git a/server/crates/arbiter-server/src/peers/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/mod.rs index a8e813c..91a6d75 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/mod.rs @@ -1,7 +1,10 @@ use crate::{ - actors::GlobalActors, - crypto::integrity::{self, Integrable}, - db::{self, DatabaseError}, + actors::{ + GlobalActors, + vault::{GetState, Vault}, + }, + crypto::integrity::{self, AttestationStatus, Integrable}, + db::{self, DatabaseError, DatabasePool}, peers::client::ClientProfile, }; use arbiter_crypto::authn; @@ -11,7 +14,7 @@ pub use auth::authenticate; use kameo::actor::{ActorRef, Spawn as _}; pub use session::UserAgentSession; use tokio::sync::oneshot; -use tracing::warn; +use tracing::{error, warn}; use vault_gate::VaultGate; use crate::crypto::integrity::hashing::Hashable; @@ -20,24 +23,11 @@ pub mod auth; pub mod session; pub mod vault_gate; -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct Credentials { pub id: i32, pub pubkey: authn::PublicKey, } -impl Hashable for Credentials { - fn hash(&self, hasher: &mut H) { - self.id.hash(hasher); - self.pubkey.hash(hasher); - } -} - -#[derive(Debug, Clone)] -pub struct AuthCredentials { - pub creds: Credentials, - // denotes new nonce, not current - pub new_nonce: i32, -} impl Hashable for authn::PublicKey { fn hash(&self, hasher: &mut H) { @@ -45,14 +35,14 @@ impl Hashable for authn::PublicKey { } } -impl Hashable for AuthCredentials { +impl Hashable for Credentials { fn hash(&self, hasher: &mut H) { - self.creds.hash(hasher); - self.new_nonce.hash(hasher); + self.id.hash(hasher); + self.pubkey.hash(hasher); } } -impl Integrable for AuthCredentials { +impl Integrable for Credentials { const KIND: &'static str = "useragent_credentials"; } @@ -95,38 +85,44 @@ impl From for Error { } } -pub async fn start( - props: &mut UserAgentConnection, - mut transport: T, - oob_sender: Box>, -) -> Result, Error> -where - T: Bi> + Send, - T: Bi> + Send, -{ - let auth_creds = authenticate(props, &mut transport).await?; - - let creds = match integrity::is_signing_available(&props.actors.vault) +async fn verify_integrity( + db: &DatabasePool, + vault: &ActorRef, + credentials: &Credentials, +) -> Result<(), Error> { + let mut conn = db + .get() .await - .map_err(|_| Error::Internal("Integrity verification failed".into()))? - { - // credentials were checked by `auth` stage - true => auth_creds.creds, - false => run_vault_gate(props, &mut transport, auth_creds).await?, - }; + .map_err(|_| Error::Internal("DB unavailable".into()))?; + match integrity::verify_entity(&mut conn, &vault, credentials, credentials.id).await { + Ok(AttestationStatus::Attested) => Ok(()), + Ok(AttestationStatus::Unavailable) => { + Err(Error::Internal("Vault sealed during promotion".into())) + } + Err(e) => { + error!(?e, "Integrity verification failed during unseal promotion"); + Err(Error::Internal("Integrity check failed".into())) + } + } +} - Ok(UserAgentSession::spawn(UserAgentSession::new( - props.clone(), - creds, - oob_sender, - ))) +async fn should_run_gate(vault: &ActorRef) -> Result { + let vault_state = vault + .ask(GetState {}) + .await + .map_err(|_| Error::Internal("Failed to contact the vault".into()))?; + + Ok(!matches!( + vault_state, + crate::actors::vault::VaultState::Unsealed + )) } async fn run_vault_gate( props: &UserAgentConnection, transport: &mut T, - auth_creds: AuthCredentials, -) -> Result + auth_creds: Credentials, +) -> Result<(), Error> where T: Bi> + Send + ?Sized, { @@ -175,3 +171,29 @@ where gate.kill(); result } + +pub async fn start( + props: &mut UserAgentConnection, + mut transport: T, + oob_sender: Box>, +) -> Result, Error> +where + T: Bi> + Send, + T: Bi> + Send, +{ + let creds = authenticate(props, &mut transport).await?; + + // should run vault gate only if sealed / unbootstrapped + if should_run_gate(&props.actors.vault).await? { + run_vault_gate(props, &mut transport, creds.clone()).await?; + } + + // checking the integrity + verify_integrity(&props.db, &props.actors.vault, &creds).await?; + + Ok(UserAgentSession::spawn(UserAgentSession::new( + props.clone(), + creds, + oob_sender, + ))) +} diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs b/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs index 53b65e0..1fcbd74 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs @@ -1,27 +1,22 @@ -use std::sync::Mutex; use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature}; use arbiter_crypto::{ authn, - safecell::{SafeCell, SafeCellHandle as _}, + safecell::SafeCellHandle as _, }; -use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; +use chacha20poly1305::aead::KeyInit; use diesel::{ExpressionMethods as _, QueryDsl as _, SelectableHelper}; use diesel_async::{AsyncConnection, RunQueryDsl}; use kameo::error::SendError; use kameo::messages; use kameo::prelude::Context; -use tracing::{error, info}; -use x25519_dalek::{EphemeralSecret, PublicKey}; +use tracing::error; use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer; -use crate::actors::{ - evm::{ +use crate::actors::evm::{ ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, UseragentCreateGrant, UseragentListGrants, - }, - vault::{self, Bootstrap, TryUnseal}, -}; + }; use crate::db::models::{ EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata, }; diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs index 7356d04..d603bff 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs @@ -1,12 +1,9 @@ use arbiter_crypto::authn; -use diesel::{ExpressionMethods, QueryDsl}; -use diesel_async::{RunQueryDsl}; -use kameo_actors::message_bus::Register; use std::{borrow::Cow, collections::HashMap}; use arbiter_proto::transport::Sender; -use kameo::{Actor, actor::ActorRef, messages, prelude::Message}; +use kameo::{Actor, actor::ActorRef, messages}; use thiserror::Error; use tracing::error; @@ -14,8 +11,8 @@ use crate::{ actors::{ flow_coordinator::client_connect_approval::ClientApprovalController, useragent_registry::ConnectUseragent, - vault::events, - }, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::{AuthCredentials, Credentials}} + }, + peers::{client::ClientProfile, user_agent::Credentials}, }; use super::{OutOfBand, UserAgentConnection}; diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs index dfd461c..28ce4d0 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs @@ -1,22 +1,24 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; +use arbiter_proto::transport::Bi; use chacha20poly1305::{AeadInPlace, KeyInit as _, XChaCha20Poly1305, XNonce}; use kameo::{Actor, error::SendError, messages, prelude::Message}; use kameo_actors::message_bus::Register; use tokio::sync::oneshot; -use tracing::{error, info}; +use tracing::{error, info, warn}; use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; pub mod state; use state::*; -use super::{AuthCredentials, Credentials}; +use super::Credentials; use crate::{ actors::{ GlobalActors, vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events}, }, - crypto::integrity::{self, AttestationStatus}, + crypto::integrity::{self}, db::DatabasePool, + peers::user_agent::UserAgentConnection, }; #[derive(Debug, thiserror::Error)] @@ -43,8 +45,8 @@ pub struct HandshakeResponse { } pub struct VaultGate { - pub auth_creds: AuthCredentials, - pub promotion_tx: Option>>, + pub auth_creds: Credentials, + pub promotion_tx: Option>>, pub state: State, pub actors: GlobalActors, pub db: DatabasePool, @@ -52,10 +54,10 @@ pub struct VaultGate { impl VaultGate { pub fn new( - auth_creds: AuthCredentials, + auth_creds: Credentials, actors: GlobalActors, db: DatabasePool, - promotion_tx: oneshot::Sender>, + promotion_tx: oneshot::Sender>, ) -> Self { Self { auth_creds, @@ -260,14 +262,14 @@ impl Message for VaultGate { &mut conn, &self.actors.vault, &self.auth_creds, - self.auth_creds.creds.id, + self.auth_creds.id, ) .await .map_err(|e| { error!(?e, "Failed to sign integrity envelope on bootstrap"); Error::internal("Integrity sign failed") })?; - Ok(self.auth_creds.creds.clone()) + Ok(()) } .await; @@ -286,34 +288,8 @@ impl Message for VaultGate { _: events::Unsealed, ctx: &mut kameo::prelude::Context, ) -> Self::Reply { - let result = async { - let mut conn = self - .db - .get() - .await - .map_err(|_| Error::internal("DB unavailable"))?; - match integrity::verify_entity( - &mut conn, - &self.actors.vault, - &self.auth_creds, - self.auth_creds.creds.id, - ) - .await - { - Ok(AttestationStatus::Attested) => Ok(self.auth_creds.creds.clone()), - Ok(AttestationStatus::Unavailable) => { - Err(Error::internal("Vault sealed during promotion")) - } - Err(e) => { - error!(?e, "Integrity verification failed during unseal promotion"); - Err(Error::InvalidKey) - } - } - } - .await; - if let Some(tx) = self.promotion_tx.take() { - let _ = tx.send(result); + let _ = tx.send(Ok(())); } ctx.stop(); } diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs index 214f877..cc38dff 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs @@ -1,6 +1,5 @@ -use std::sync::Mutex; -use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; +use x25519_dalek::{PublicKey, SharedSecret}; pub struct Handshake { client_pubkey: PublicKey, diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 71f989c..a6b0773 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -1,5 +1,5 @@ use arbiter_crypto::{ - authn::{self, CLIENT_CONTEXT, format_challenge}, + authn::{self, AuthChallenge, CLIENT_CONTEXT}, safecell::{SafeCell, SafeCellHandle as _}, }; use arbiter_proto::ClientMetadata; @@ -66,12 +66,8 @@ async fn insert_registered_client( .unwrap(); } -fn sign_client_challenge( - key: &SigningKey, - nonce: i32, - pubkey: &authn::PublicKey, -) -> authn::Signature { - let challenge = format_challenge(nonce, &pubkey.to_bytes()); +fn sign_client_challenge(key: &SigningKey, challenge: &AuthChallenge) -> authn::Signature { + let challenge = challenge.format(); key.signing_key() .sign_deterministic(&challenge, CLIENT_CONTEXT) .unwrap() diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index cfc4b53..d461aa3 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -8,7 +8,7 @@ use arbiter_server::{ actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, crypto::integrity, db::{self, schema}, - peers::user_agent::{AuthCredentials, Credentials, UserAgentConnection, auth}, + peers::user_agent::{Credentials, Credentials, UserAgentConnection, auth}, }; use diesel::{ExpressionMethods as _, QueryDsl, insert_into}; use diesel_async::RunQueryDsl; @@ -144,7 +144,7 @@ pub async fn test_challenge_auth() { integrity::sign_entity( &mut conn, &actors.vault, - &AuthCredentials { + &Credentials { creds: Credentials { id, pubkey: new_key.verifying_key().into(), @@ -285,7 +285,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { integrity::sign_entity( &mut conn, &actors.vault, - &AuthCredentials { + &Credentials { creds: Credentials { id, pubkey: new_key.verifying_key().into(), diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index b63de98..ae7ee7a 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -9,8 +9,10 @@ use arbiter_server::{ }, db, peers::user_agent::{ - AuthCredentials, Credentials, - vault_gate::{Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate}, + Credentials, + vault_gate::{ + Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate, + }, }, }; @@ -21,7 +23,11 @@ use x25519_dalek::{EphemeralSecret, PublicKey}; async fn setup_sealed_gate( seal_key: &[u8], -) -> (db::DatabasePool, kameo::actor::ActorRef, oneshot::Receiver>) { +) -> ( + db::DatabasePool, + kameo::actor::ActorRef, + oneshot::Receiver>, +) { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); @@ -36,10 +42,7 @@ async fn setup_sealed_gate( let (promotion_tx, promotion_rx) = oneshot::channel(); let pubkey = authn::SigningKey::generate().public_key(); - let auth_creds = AuthCredentials { - creds: Credentials { id: 1, pubkey }, - new_nonce: 1, - }; + let auth_creds = Credentials { id: 1, pubkey }; let gate = VaultGate::spawn(VaultGate::new(auth_creds, actors, db.clone(), promotion_tx)); (db, gate, promotion_rx) From 790026e93b1bf5dd0dc3856b95094326fb5e3bbd Mon Sep 17 00:00:00 2001 From: Skipper Date: Fri, 17 Apr 2026 17:49:06 +0200 Subject: [PATCH 11/13] fix(server::tests): api surface of auth challenge changed --- .../2026-02-14-171124-0000_init/up.sql | 2 +- server/crates/arbiter-server/src/db/schema.rs | 1 - .../arbiter-server/tests/client/auth.rs | 22 +- .../arbiter-server/tests/user_agent/auth.rs | 234 +++++++++++++++--- .../arbiter-server/tests/user_agent/unseal.rs | 2 +- .../arbiter-server/tests/vault/lifecycle.rs | 2 +- 6 files changed, 207 insertions(+), 56 deletions(-) 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 dfc64d3..4913a2d 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 @@ -49,7 +49,7 @@ create table if not exists useragent_client ( created_at integer not null default(unixepoch ('now')), updated_at integer not null default(unixepoch ('now')) ) STRICT; -create unique index if not exists uniq_useragent_client_public_key on useragent_client (public_key, key_type); +create unique index if not exists uniq_useragent_client_public_key on useragent_client (public_key); create table if not exists client_metadata ( id integer not null primary key, diff --git a/server/crates/arbiter-server/src/db/schema.rs b/server/crates/arbiter-server/src/db/schema.rs index f02c036..6d1c6b2 100644 --- a/server/crates/arbiter-server/src/db/schema.rs +++ b/server/crates/arbiter-server/src/db/schema.rs @@ -189,7 +189,6 @@ diesel::table! { useragent_client (id) { id -> Integer, public_key -> Binary, - key_type -> Integer, created_at -> Integer, updated_at -> Integer, } diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index a6b0773..b106d34 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -58,7 +58,6 @@ async fn insert_registered_client( &actors.vault, &ClientCredentials { pubkey: pubkey.into(), - nonce: 1, }, client_id, ) @@ -82,10 +81,7 @@ async fn insert_bootstrap_sentinel_useragent(db: &db::DatabasePool) { .to_vec(); insert_into(schema::useragent_client::table) - .values(( - schema::useragent_client::public_key.eq(sentinel_key), - schema::useragent_client::key_type.eq(1i32), - )) + .values((schema::useragent_client::public_key.eq(sentinel_key),)) .execute(&mut conn) .await .unwrap(); @@ -171,14 +167,14 @@ pub async fn test_challenge_auth() { .expect("should receive challenge"); let challenge = match response { Ok(resp) => match resp { - auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce), + auth::Outbound::AuthChallenge { challenge } => challenge, other => panic!("Expected AuthChallenge, got {other:?}"), }, Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; // Sign the challenge and send solution - let signature = sign_client_challenge(&new_key, challenge.1, &challenge.0); + let signature = sign_client_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { signature }) @@ -226,11 +222,11 @@ pub async fn test_metadata_unchanged_does_not_append_history() { .unwrap(); let response = test_transport.recv().await.unwrap().unwrap(); - let (pubkey, nonce) = match response { - auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce), + let challenge = match response { + auth::Outbound::AuthChallenge { challenge } => challenge, other => panic!("Expected AuthChallenge, got {other:?}"), }; - let signature = sign_client_challenge(&new_key, nonce, &pubkey); + let signature = sign_client_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { signature }) .await @@ -288,11 +284,11 @@ pub async fn test_metadata_change_appends_history_and_repoints_binding() { .unwrap(); let response = test_transport.recv().await.unwrap().unwrap(); - let (pubkey, nonce) = match response { - auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce), + let challenge = match response { + auth::Outbound::AuthChallenge { challenge } => challenge, other => panic!("Expected AuthChallenge, got {other:?}"), }; - let signature = sign_client_challenge(&new_key, nonce, &pubkey); + let signature = sign_client_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { signature }) .await diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index d461aa3..5c9c247 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -1,33 +1,145 @@ use arbiter_crypto::{ - authn::{self, USERAGENT_CONTEXT, format_challenge}, + authn::{self, AuthChallenge, USERAGENT_CONTEXT}, safecell::{SafeCell, SafeCellHandle as _}, }; -use arbiter_proto::transport::{Receiver, Sender}; +use arbiter_proto::transport::{Error as TransportError, Receiver, Sender}; use arbiter_server::{ actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, crypto::integrity, db::{self, schema}, - peers::user_agent::{Credentials, Credentials, UserAgentConnection, auth}, + peers::user_agent::{self, Credentials, UserAgentConnection, auth, vault_gate}, }; +use async_trait::async_trait; use diesel::{ExpressionMethods as _, QueryDsl, insert_into}; use diesel_async::RunQueryDsl; use ml_dsa::{KeyGen, MlDsa87, SigningKey, signature::Keypair as _}; +use tokio::sync::mpsc; use super::common::ChannelTransport; fn sign_useragent_challenge( key: &SigningKey, - nonce: i32, - pubkey_bytes: &[u8], + challenge: &AuthChallenge, ) -> authn::Signature { - let challenge = format_challenge(nonce, pubkey_bytes); + let challenge = challenge.format(); key.signing_key() .sign_deterministic(&challenge, USERAGENT_CONTEXT) .unwrap() .into() } +fn tamper_challenge(challenge: &AuthChallenge) -> AuthChallenge { + let mut challenge = challenge.clone(); + challenge.nonce[0] ^= 1; + challenge +} + +struct NullOobSender; + +#[async_trait] +impl Sender for NullOobSender { + async fn send(&mut self, _item: user_agent::OutOfBand) -> Result<(), TransportError> { + Ok(()) + } +} + +struct StartServerTransport { + auth_rx: mpsc::Receiver, + auth_tx: mpsc::Sender>, + vault_rx: mpsc::Receiver, + vault_tx: mpsc::Sender>, +} + +struct StartTestTransport { + auth_rx: mpsc::Receiver>, + auth_tx: mpsc::Sender, +} + +fn start_transport_pair() -> (StartServerTransport, StartTestTransport) { + let (auth_in_tx, auth_in_rx) = mpsc::channel(10); + let (auth_out_tx, auth_out_rx) = mpsc::channel(10); + let (_vault_in_tx, vault_in_rx) = mpsc::channel(10); + let (vault_out_tx, _vault_out_rx) = mpsc::channel(10); + + ( + StartServerTransport { + auth_rx: auth_in_rx, + auth_tx: auth_out_tx, + vault_rx: vault_in_rx, + vault_tx: vault_out_tx, + }, + StartTestTransport { + auth_rx: auth_out_rx, + auth_tx: auth_in_tx, + }, + ) +} + +#[async_trait] +impl Receiver for StartServerTransport { + async fn recv(&mut self) -> Option { + self.auth_rx.recv().await + } +} + +#[async_trait] +impl Sender> for StartServerTransport { + async fn send(&mut self, item: Result) -> Result<(), TransportError> { + self.auth_tx + .send(item) + .await + .map_err(|_| TransportError::ChannelClosed) + } +} + +impl arbiter_proto::transport::Bi> + for StartServerTransport +{ +} + +#[async_trait] +impl Receiver for StartServerTransport { + async fn recv(&mut self) -> Option { + self.vault_rx.recv().await + } +} + +#[async_trait] +impl Sender> for StartServerTransport { + async fn send( + &mut self, + item: Result, + ) -> Result<(), TransportError> { + self.vault_tx + .send(item) + .await + .map_err(|_| TransportError::ChannelClosed) + } +} + +impl arbiter_proto::transport::Bi> + for StartServerTransport +{ +} + +#[async_trait] +impl Receiver> for StartTestTransport { + async fn recv(&mut self) -> Option> { + self.auth_rx.recv().await + } +} + +#[async_trait] +impl Sender for StartTestTransport { + async fn send(&mut self, item: auth::Inbound) -> Result<(), TransportError> { + self.auth_tx + .send(item) + .await + .map_err(|_| TransportError::ChannelClosed) + } +} + #[tokio::test] #[test_log::test] pub async fn test_bootstrap_token_auth() { @@ -58,14 +170,29 @@ pub async fn test_bootstrap_token_auth() { .await .unwrap(); + let response = test_transport + .recv() + .await + .expect("should receive challenge"); + let challenge = match response { + Ok(auth::Outbound::AuthChallenge { challenge }) => challenge, + other => panic!("Expected AuthChallenge, got {other:?}"), + }; + + let signature = sign_useragent_challenge(&new_key, &challenge); + + test_transport + .send(auth::Inbound::AuthChallengeSolution { + signature: signature.to_bytes(), + }) + .await + .unwrap(); + let response = test_transport .recv() .await .expect("should receive auth result"); - match response { - Ok(auth::Outbound::AuthSuccess) => {} - other => panic!("Expected AuthSuccess, got {other:?}"), - } + assert!(matches!(response, Ok(auth::Outbound::AuthSuccess))); task.await.unwrap().unwrap(); @@ -100,6 +227,23 @@ pub async fn test_bootstrap_invalid_token_auth() { .await .unwrap(); + let response = test_transport + .recv() + .await + .expect("should receive challenge"); + let challenge = match response { + Ok(auth::Outbound::AuthChallenge { challenge }) => challenge, + other => panic!("Expected AuthChallenge, got {other:?}"), + }; + + let signature = sign_useragent_challenge(&new_key, &challenge); + test_transport + .send(auth::Inbound::AuthChallengeSolution { + signature: signature.to_bytes(), + }) + .await + .unwrap(); + assert!(matches!( task.await.unwrap(), Err(auth::Error::InvalidBootstrapToken) @@ -133,10 +277,7 @@ pub async fn test_challenge_auth() { { let mut conn = db.get().await.unwrap(); let id: i32 = insert_into(schema::useragent_client::table) - .values(( - schema::useragent_client::public_key.eq(pubkey_bytes.clone()), - schema::useragent_client::key_type.eq(1i32), - )) + .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) .returning(schema::useragent_client::id) .get_result(&mut conn) .await @@ -145,11 +286,8 @@ pub async fn test_challenge_auth() { &mut conn, &actors.vault, &Credentials { - creds: Credentials { - id, - pubkey: new_key.verifying_key().into(), - }, - new_nonce: 1, + id, + pubkey: new_key.verifying_key().into(), }, id, ) @@ -178,13 +316,13 @@ pub async fn test_challenge_auth() { .expect("should receive challenge"); let challenge = match response { Ok(resp) => match resp { - auth::Outbound::AuthChallenge { nonce } => nonce, + auth::Outbound::AuthChallenge { challenge } => challenge, other => panic!("Expected AuthChallenge, got {other:?}"), }, Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - let signature = sign_useragent_challenge(&new_key, challenge, &pubkey_bytes); + let signature = sign_useragent_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { @@ -225,20 +363,17 @@ pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { let mut conn = db.get().await.unwrap(); insert_into(schema::useragent_client::table) - .values(( - schema::useragent_client::public_key.eq(pubkey_bytes.clone()), - schema::useragent_client::key_type.eq(1i32), - )) + .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) .execute(&mut conn) .await .unwrap(); } - let (mut server_transport, mut test_transport) = ChannelTransport::new(); + let (server_transport, mut test_transport) = start_transport_pair(); let db_for_task = db.clone(); let task = tokio::spawn(async move { let mut props = UserAgentConnection::new(db_for_task, actors); - auth::authenticate(&mut props, &mut server_transport).await + user_agent::start(&mut props, server_transport, Box::new(NullOobSender)).await }); test_transport @@ -249,9 +384,36 @@ pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() .await .unwrap(); + let response = test_transport + .recv() + .await + .expect("should receive challenge"); + let challenge = match response { + Ok(resp) => match resp { + auth::Outbound::AuthChallenge { challenge } => challenge, + other => panic!("Expected AuthChallenge, got {other:?}"), + }, + Err(err) => panic!("Expected Ok response, got Err({err:?})"), + }; + + let signature = sign_useragent_challenge(&new_key, &challenge); + + test_transport + .send(auth::Inbound::AuthChallengeSolution { + signature: signature.to_bytes(), + }) + .await + .unwrap(); + + let response = test_transport + .recv() + .await + .expect("should receive auth result"); + assert!(matches!(response, Ok(auth::Outbound::AuthSuccess))); + assert!(matches!( task.await.unwrap(), - Err(auth::Error::Internal { .. }) + Err(user_agent::Error::Internal(_)) )); } @@ -274,10 +436,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { { let mut conn = db.get().await.unwrap(); let id: i32 = insert_into(schema::useragent_client::table) - .values(( - schema::useragent_client::public_key.eq(pubkey_bytes.clone()), - schema::useragent_client::key_type.eq(1i32), - )) + .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) .returning(schema::useragent_client::id) .get_result(&mut conn) .await @@ -286,11 +445,8 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { &mut conn, &actors.vault, &Credentials { - creds: Credentials { - id, - pubkey: new_key.verifying_key().into(), - }, - new_nonce: 1, + id, + pubkey: new_key.verifying_key().into(), }, id, ) @@ -319,13 +475,13 @@ pub async fn test_challenge_auth_rejects_invalid_signature() { .expect("should receive challenge"); let challenge = match response { Ok(resp) => match resp { - auth::Outbound::AuthChallenge { nonce } => nonce, + auth::Outbound::AuthChallenge { challenge } => challenge, other => panic!("Expected AuthChallenge, got {other:?}"), }, Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - let signature = sign_useragent_challenge(&new_key, challenge + 1, &pubkey_bytes); + let signature = sign_useragent_challenge(&new_key, &tamper_challenge(&challenge)); test_transport .send(auth::Inbound::AuthChallengeSolution { diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/user_agent/unseal.rs index ae7ee7a..6772b79 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/user_agent/unseal.rs @@ -26,7 +26,7 @@ async fn setup_sealed_gate( ) -> ( db::DatabasePool, kameo::actor::ActorRef, - oneshot::Receiver>, + oneshot::Receiver>, ) { let db = db::create_test_pool().await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); diff --git a/server/crates/arbiter-server/tests/vault/lifecycle.rs b/server/crates/arbiter-server/tests/vault/lifecycle.rs index ddfb602..77238c2 100644 --- a/server/crates/arbiter-server/tests/vault/lifecycle.rs +++ b/server/crates/arbiter-server/tests/vault/lifecycle.rs @@ -87,7 +87,7 @@ async fn test_new_restores_sealed_state() { .await .unwrap(); let err = actor2.decrypt(1).await.unwrap_err(); - assert!(matches!(err, Error::NotBootstrapped)); + assert!(matches!(err, Error::Sealed)); } #[tokio::test] From 9ee86afc199a72e31bf9ec517c0318b71f639cc2 Mon Sep 17 00:00:00 2001 From: Skipper Date: Fri, 17 Apr 2026 18:19:39 +0200 Subject: [PATCH 12/13] fix(useragent): now using new challenge format --- server/crates/arbiter-crypto/src/authn/v1.rs | 8 +++ useragent/lib/features/connection/auth.dart | 12 ++-- useragent/lib/proto/client/auth.pb.dart | 31 +++++----- useragent/lib/proto/client/auth.pbjson.dart | 8 +-- useragent/lib/proto/user_agent/auth.pb.dart | 30 +++++++--- .../lib/proto/user_agent/auth.pbjson.dart | 8 ++- useragent/lib/src/rust/api.dart | 8 +++ useragent/lib/src/rust/frb_generated.dart | 59 ++++++++++++++++++- useragent/lib/src/rust/frb_generated.io.dart | 9 +++ useragent/lib/src/rust/frb_generated.web.dart | 9 +++ useragent/mise.toml | 4 ++ useragent/rust/Cargo.lock | 46 ++++++++++++--- useragent/rust/src/api/mod.rs | 15 ++++- useragent/rust/src/frb_generated.rs | 51 +++++++++++++++- 14 files changed, 251 insertions(+), 47 deletions(-) create mode 100644 useragent/mise.toml diff --git a/server/crates/arbiter-crypto/src/authn/v1.rs b/server/crates/arbiter-crypto/src/authn/v1.rs index f192e76..5f21169 100644 --- a/server/crates/arbiter-crypto/src/authn/v1.rs +++ b/server/crates/arbiter-crypto/src/authn/v1.rs @@ -43,6 +43,14 @@ impl AuthChallenge { buffer } } + + pub fn from_parts(nonce: &[u8], timestamp: i64) -> Result { + let random_nonce = nonce.as_array().ok_or(())?; + Ok(AuthChallenge { + nonce: *random_nonce, + timestamp: DateTime::from_timestamp_nanos(timestamp), + }) + } } pub type KeyParams = MlDsa87; diff --git a/useragent/lib/features/connection/auth.dart b/useragent/lib/features/connection/auth.dart index f7e50a8..84cb3d9 100644 --- a/useragent/lib/features/connection/auth.dart +++ b/useragent/lib/features/connection/auth.dart @@ -7,6 +7,7 @@ import 'package:arbiter/features/identity/pk_manager.dart'; import 'package:arbiter/proto/arbiter.pbgrpc.dart'; import 'package:arbiter/proto/user_agent/auth.pb.dart' as ua_auth; import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/src/rust/api.dart'; import 'package:grpc/grpc.dart'; import 'package:mtcore/markettakers.dart'; @@ -92,7 +93,10 @@ Future connectAndAuthorize( ); } - final challenge = _formatChallenge(authResponse.challenge, pubkey); + final challenge = await formatChallenge( + random: authResponse.challenge.random, + timestamp: authResponse.challenge.timestampNanos.toInt(), + ); talker.info( 'Received auth challenge, signing with key ${base64Encode(pubkey)}', ); @@ -164,9 +168,3 @@ Future _connect(StoredServerInfo serverInfo) async { return Connection(channel: channel, tx: tx, rx: rx); } - -List _formatChallenge(ua_auth.AuthChallenge challenge, List pubkey) { - final encodedPubkey = base64Encode(pubkey); - final payload = "${challenge.nonce}:$encodedPubkey"; - return utf8.encode(payload); -} diff --git a/useragent/lib/proto/client/auth.pb.dart b/useragent/lib/proto/client/auth.pb.dart index 98eaab1..ae19314 100644 --- a/useragent/lib/proto/client/auth.pb.dart +++ b/useragent/lib/proto/client/auth.pb.dart @@ -12,6 +12,7 @@ import 'dart:core' as $core; +import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:protobuf/protobuf.dart' as $pb; import '../shared/client.pb.dart' as $0; @@ -94,12 +95,12 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { class AuthChallenge extends $pb.GeneratedMessage { factory AuthChallenge({ - $core.List<$core.int>? pubkey, - $core.int? nonce, + $fixnum.Int64? timestampNanos, + $core.List<$core.int>? random, }) { final result = create(); - if (pubkey != null) result.pubkey = pubkey; - if (nonce != null) result.nonce = nonce; + if (timestampNanos != null) result.timestampNanos = timestampNanos; + if (random != null) result.random = random; return result; } @@ -117,9 +118,11 @@ class AuthChallenge extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'), createEmptyInstance: create) + ..a<$fixnum.Int64>( + 1, _omitFieldNames ? '' : 'timestampNanos', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) ..a<$core.List<$core.int>>( - 1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY) - ..aI(2, _omitFieldNames ? '' : 'nonce') + 2, _omitFieldNames ? '' : 'random', $pb.PbFieldType.OY) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -142,22 +145,22 @@ class AuthChallenge extends $pb.GeneratedMessage { static AuthChallenge? _defaultInstance; @$pb.TagNumber(1) - $core.List<$core.int> get pubkey => $_getN(0); + $fixnum.Int64 get timestampNanos => $_getI64(0); @$pb.TagNumber(1) - set pubkey($core.List<$core.int> value) => $_setBytes(0, value); + set timestampNanos($fixnum.Int64 value) => $_setInt64(0, value); @$pb.TagNumber(1) - $core.bool hasPubkey() => $_has(0); + $core.bool hasTimestampNanos() => $_has(0); @$pb.TagNumber(1) - void clearPubkey() => $_clearField(1); + void clearTimestampNanos() => $_clearField(1); @$pb.TagNumber(2) - $core.int get nonce => $_getIZ(1); + $core.List<$core.int> get random => $_getN(1); @$pb.TagNumber(2) - set nonce($core.int value) => $_setSignedInt32(1, value); + set random($core.List<$core.int> value) => $_setBytes(1, value); @$pb.TagNumber(2) - $core.bool hasNonce() => $_has(1); + $core.bool hasRandom() => $_has(1); @$pb.TagNumber(2) - void clearNonce() => $_clearField(2); + void clearRandom() => $_clearField(2); } class AuthChallengeSolution extends $pb.GeneratedMessage { diff --git a/useragent/lib/proto/client/auth.pbjson.dart b/useragent/lib/proto/client/auth.pbjson.dart index c9cf99e..c7b42b7 100644 --- a/useragent/lib/proto/client/auth.pbjson.dart +++ b/useragent/lib/proto/client/auth.pbjson.dart @@ -62,15 +62,15 @@ final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Deco const AuthChallenge$json = { '1': 'AuthChallenge', '2': [ - {'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'}, - {'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'}, + {'1': 'timestamp_nanos', '3': 1, '4': 1, '5': 4, '10': 'timestampNanos'}, + {'1': 'random', '3': 2, '4': 1, '5': 12, '10': 'random'}, ], }; /// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode( - 'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg' - 'Vub25jZQ=='); + 'Cg1BdXRoQ2hhbGxlbmdlEicKD3RpbWVzdGFtcF9uYW5vcxgBIAEoBFIOdGltZXN0YW1wTmFub3' + 'MSFgoGcmFuZG9tGAIgASgMUgZyYW5kb20='); @$core.Deprecated('Use authChallengeSolutionDescriptor instead') const AuthChallengeSolution$json = { diff --git a/useragent/lib/proto/user_agent/auth.pb.dart b/useragent/lib/proto/user_agent/auth.pb.dart index 1fa8bbd..c89b8c5 100644 --- a/useragent/lib/proto/user_agent/auth.pb.dart +++ b/useragent/lib/proto/user_agent/auth.pb.dart @@ -12,6 +12,7 @@ import 'dart:core' as $core; +import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:protobuf/protobuf.dart' as $pb; import 'auth.pbenum.dart'; @@ -90,10 +91,12 @@ class AuthChallengeRequest extends $pb.GeneratedMessage { class AuthChallenge extends $pb.GeneratedMessage { factory AuthChallenge({ - $core.int? nonce, + $fixnum.Int64? timestampNanos, + $core.List<$core.int>? random, }) { final result = create(); - if (nonce != null) result.nonce = nonce; + if (timestampNanos != null) result.timestampNanos = timestampNanos; + if (random != null) result.random = random; return result; } @@ -111,7 +114,11 @@ class AuthChallenge extends $pb.GeneratedMessage { package: const $pb.PackageName( _omitMessageNames ? '' : 'arbiter.user_agent.auth'), createEmptyInstance: create) - ..aI(1, _omitFieldNames ? '' : 'nonce') + ..a<$fixnum.Int64>( + 1, _omitFieldNames ? '' : 'timestampNanos', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$core.List<$core.int>>( + 2, _omitFieldNames ? '' : 'random', $pb.PbFieldType.OY) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -134,13 +141,22 @@ class AuthChallenge extends $pb.GeneratedMessage { static AuthChallenge? _defaultInstance; @$pb.TagNumber(1) - $core.int get nonce => $_getIZ(0); + $fixnum.Int64 get timestampNanos => $_getI64(0); @$pb.TagNumber(1) - set nonce($core.int value) => $_setSignedInt32(0, value); + set timestampNanos($fixnum.Int64 value) => $_setInt64(0, value); @$pb.TagNumber(1) - $core.bool hasNonce() => $_has(0); + $core.bool hasTimestampNanos() => $_has(0); @$pb.TagNumber(1) - void clearNonce() => $_clearField(1); + void clearTimestampNanos() => $_clearField(1); + + @$pb.TagNumber(2) + $core.List<$core.int> get random => $_getN(1); + @$pb.TagNumber(2) + set random($core.List<$core.int> value) => $_setBytes(1, value); + @$pb.TagNumber(2) + $core.bool hasRandom() => $_has(1); + @$pb.TagNumber(2) + void clearRandom() => $_clearField(2); } class AuthChallengeSolution extends $pb.GeneratedMessage { diff --git a/useragent/lib/proto/user_agent/auth.pbjson.dart b/useragent/lib/proto/user_agent/auth.pbjson.dart index 7f7fe15..87d0979 100644 --- a/useragent/lib/proto/user_agent/auth.pbjson.dart +++ b/useragent/lib/proto/user_agent/auth.pbjson.dart @@ -67,13 +67,15 @@ final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Deco const AuthChallenge$json = { '1': 'AuthChallenge', '2': [ - {'1': 'nonce', '3': 1, '4': 1, '5': 5, '10': 'nonce'}, + {'1': 'timestamp_nanos', '3': 1, '4': 1, '5': 4, '10': 'timestampNanos'}, + {'1': 'random', '3': 2, '4': 1, '5': 12, '10': 'random'}, ], }; /// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List authChallengeDescriptor = $convert - .base64Decode('Cg1BdXRoQ2hhbGxlbmdlEhQKBW5vbmNlGAEgASgFUgVub25jZQ=='); +final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode( + 'Cg1BdXRoQ2hhbGxlbmdlEicKD3RpbWVzdGFtcF9uYW5vcxgBIAEoBFIOdGltZXN0YW1wTmFub3' + 'MSFgoGcmFuZG9tGAIgASgMUgZyYW5kb20='); @$core.Deprecated('Use authChallengeSolutionDescriptor instead') const AuthChallengeSolution$json = { diff --git a/useragent/lib/src/rust/api.dart b/useragent/lib/src/rust/api.dart index 0c22fbf..8648c0e 100644 --- a/useragent/lib/src/rust/api.dart +++ b/useragent/lib/src/rust/api.dart @@ -6,6 +6,14 @@ import 'frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +Future formatChallenge({ + required List random, + required PlatformInt64 timestamp, +}) => RustLib.instance.api.crateApiFormatChallenge( + random: random, + timestamp: timestamp, +); + // Rust type: RustOpaqueMoi> abstract class MldsaKey implements RustOpaqueInterface { static Future fromBytes({required List bytes}) => diff --git a/useragent/lib/src/rust/frb_generated.dart b/useragent/lib/src/rust/frb_generated.dart index d9206e5..fa57ada 100644 --- a/useragent/lib/src/rust/frb_generated.dart +++ b/useragent/lib/src/rust/frb_generated.dart @@ -64,7 +64,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => -437661335; + int get rustContentHash => 1247923898; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -89,6 +89,11 @@ abstract class RustLibApi extends BaseApi { Future crateApiMldsaKeyToBytes({required MldsaKey that}); + Future crateApiFormatChallenge({ + required List random, + required PlatformInt64 timestamp, + }); + RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_MldsaKey; @@ -267,6 +272,40 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiMldsaKeyToBytesConstMeta => const TaskConstMeta(debugName: "MldsaKey_to_bytes", argNames: ["that"]); + @override + Future crateApiFormatChallenge({ + required List random, + required PlatformInt64 timestamp, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(random, serializer); + sse_encode_i_64(timestamp, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 6, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_prim_u_8_strict, + decodeErrorData: sse_decode_String, + ), + constMeta: kCrateApiFormatChallengeConstMeta, + argValues: [random, timestamp], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiFormatChallengeConstMeta => const TaskConstMeta( + debugName: "format_challenge", + argNames: ["random", "timestamp"], + ); + RustArcIncrementStrongCountFnType get rust_arc_increment_strong_count_MldsaKey => wire .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMldsaKey; @@ -314,6 +353,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as String; } + @protected + PlatformInt64 dco_decode_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + @protected List dco_decode_list_prim_u_8_loose(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -394,6 +439,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -491,6 +542,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + @protected void sse_encode_list_prim_u_8_loose( List self, diff --git a/useragent/lib/src/rust/frb_generated.io.dart b/useragent/lib/src/rust/frb_generated.io.dart index 942e9cd..3780c4e 100644 --- a/useragent/lib/src/rust/frb_generated.io.dart +++ b/useragent/lib/src/rust/frb_generated.io.dart @@ -46,6 +46,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -85,6 +88,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -136,6 +142,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); diff --git a/useragent/lib/src/rust/frb_generated.web.dart b/useragent/lib/src/rust/frb_generated.web.dart index 640f1f8..e8a41cb 100644 --- a/useragent/lib/src/rust/frb_generated.web.dart +++ b/useragent/lib/src/rust/frb_generated.web.dart @@ -48,6 +48,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -87,6 +90,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -138,6 +144,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); diff --git a/useragent/mise.toml b/useragent/mise.toml new file mode 100644 index 0000000..540e6c5 --- /dev/null +++ b/useragent/mise.toml @@ -0,0 +1,4 @@ +[tasks.codegen] +run = ''' + flutter_rust_bridge_codegen generate +''' diff --git a/useragent/rust/Cargo.lock b/useragent/rust/Cargo.lock index 9b8db0c..5442523 100644 --- a/useragent/rust/Cargo.lock +++ b/useragent/rust/Cargo.lock @@ -232,7 +232,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "arbiter-crypto" version = "0.1.0" dependencies = [ - "base64", + "chrono", "memsafe", "ml-dsa", "rand", @@ -487,12 +487,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.8.3" @@ -718,6 +712,20 @@ dependencies = [ "rand_core", ] +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -1821,6 +1829,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" diff --git a/useragent/rust/src/api/mod.rs b/useragent/rust/src/api/mod.rs index baaab3c..0590ab0 100644 --- a/useragent/rust/src/api/mod.rs +++ b/useragent/rust/src/api/mod.rs @@ -1,13 +1,15 @@ use anyhow::anyhow; +use arbiter_crypto::authn::{self, AuthChallenge, USERAGENT_CONTEXT}; use flutter_rust_bridge::frb; -use arbiter_crypto::authn::{self, USERAGENT_CONTEXT}; #[frb(opaque)] pub struct MldsaKey(authn::SigningKey); impl MldsaKey { pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { - let bytes: [u8; 32] = bytes.try_into().map_err(|_| anyhow!("Invalid key length"))?; + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| anyhow!("Invalid key length"))?; Ok(Self(authn::SigningKey::from_seed(bytes))) } @@ -26,4 +28,11 @@ impl MldsaKey { pub fn get_public_key(&self) -> Vec { self.0.public_key().to_bytes().to_vec() } -} \ No newline at end of file +} + +pub fn format_challenge(random: Vec, timestamp: i64) -> Result, String> { + let challenge = AuthChallenge::from_parts(&random, timestamp) + .map_err(|_| "Invalid nonce length".to_string())?; + + Ok(challenge.format()) +} diff --git a/useragent/rust/src/frb_generated.rs b/useragent/rust/src/frb_generated.rs index 7489044..46647d0 100644 --- a/useragent/rust/src/frb_generated.rs +++ b/useragent/rust/src/frb_generated.rs @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -437661335; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1247923898; // Section: executor @@ -267,6 +267,40 @@ fn wire__crate__api__MldsaKey_to_bytes_impl( }, ) } +fn wire__crate__api__format_challenge_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "format_challenge", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_random = >::sse_decode(&mut deserializer); + let api_timestamp = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::format_challenge(api_random, api_timestamp)?; + Ok(output_ok) + })()) + } + }, + ) +} // Section: related_funcs @@ -312,6 +346,13 @@ impl SseDecode for String { } } +impl SseDecode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -371,6 +412,7 @@ fn pde_ffi_dispatcher_primary_impl( 3 => wire__crate__api__MldsaKey_get_public_key_impl(port, ptr, rust_vec_len, data_len), 4 => wire__crate__api__MldsaKey_sign_impl(port, ptr, rust_vec_len, data_len), 5 => wire__crate__api__MldsaKey_to_bytes_impl(port, ptr, rust_vec_len, data_len), + 6 => wire__crate__api__format_challenge_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -436,6 +478,13 @@ impl SseEncode for String { } } +impl SseEncode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i64::(self).unwrap(); + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { From 4a8e51ef32286b5e87ca9311a5a8bd467ac804e4 Mon Sep 17 00:00:00 2001 From: Skipper Date: Fri, 17 Apr 2026 18:25:31 +0200 Subject: [PATCH 13/13] docs: updated to new auth challenge format and removed stale TOCTOU race condition note --- IMPLEMENTATION.md | 33 +++++-------------- .../2026-02-14-171124-0000_init/up.sql | 1 - 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index a1816b0..3a0bfd9 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -29,38 +29,23 @@ flowchart TD A([Client connects]) --> B[Receive AuthChallengeRequest] B --> C{pubkey in DB?} - C -- yes --> D[Read nonce\nIncrement nonce in DB] - D --> G + C -- yes --> G[Generate AuthChallenge] C -- no --> E[Ask all UserAgents:\nClientConnectionRequest] E --> F{First response} F -- denied --> Z([Reject connection]) F -- approved --> F2[Cancel remaining\nUserAgent requests] - F2 --> F3[INSERT client\nnonce = 1] - F3 --> G[Send AuthChallenge\nwith nonce] + F2 --> F3[INSERT client] + F3 --> G - G --> H[Receive AuthChallengeSolution] - H --> I{Signature valid?} - I -- no --> Z - I -- yes --> J([Session started]) + G --> H[Send AuthChallenge\ntimestamp + random bytes] + H --> I[Receive AuthChallengeSolution] + I --> K{Signature valid?} + K -- no --> Z + K -- yes --> J([Session started]) ``` -### Known Issue: Concurrent Registration Race (TOCTOU) - -Two connections presenting the same previously-unknown public key can race through the approval flow simultaneously: - -1. Both check the DB → neither is registered. -2. Both request approval from user agents → both receive approval. -3. Both `INSERT` the client record → the second insert silently overwrites the first, resetting the nonce. - -This means the first connection's nonce is invalidated by the second, causing its challenge verification to fail. A fix requires either serialising new-client registration (e.g. an in-memory lock keyed on pubkey) or replacing the separate check + insert with an `INSERT OR IGNORE` / upsert guarded by a unique constraint on `public_key`. - -### Nonce Semantics - -The `program_client.nonce` column stores the **next usable nonce** — i.e. it is always one ahead of the nonce last issued in a challenge. - -- **New client:** inserted with `nonce = 1`; the first challenge is issued with `nonce = 0`. -- **Existing client:** the current DB value is read and used as the challenge nonce, then immediately incremented within the same exclusive transaction, preventing replay. +Auth challenges are generated from fresh random bytes plus a timestamp. They are signed as the canonical challenge payload and are not persisted in `program_client`. --- 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 4913a2d..ac40bce 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 @@ -71,7 +71,6 @@ create unique index if not exists uniq_metadata_binding_client on client_metadat create table if not exists program_client ( id integer not null primary key, - nonce integer not null default(1), -- used for auth challenge public_key blob not null, metadata_id integer not null references client_metadata (id) on delete cascade, created_at integer not null default(unixepoch ('now')),