From 6987e5f70fb450a954e3046af97a382208f4c196 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Thu, 26 Mar 2026 19:57:48 +0100 Subject: [PATCH 1/5] feat(evm): implement EVM sign transaction handling in client and user agent --- protobufs/client.proto | 1 + protobufs/user_agent.proto | 7 + server/crates/arbiter-client/src/transport.rs | 8 +- .../crates/arbiter-client/src/wallets/evm.rs | 68 +++++- .../arbiter-server/src/actors/client/auth.rs | 35 ++- .../arbiter-server/src/actors/client/mod.rs | 10 +- .../src/actors/client/session.rs | 46 +++- .../src/actors/user_agent/session.rs | 10 +- .../actors/user_agent/session/connection.rs | 49 +++- .../src/evm/policies/ether_transfer/mod.rs | 4 +- .../src/evm/policies/token_transfers/mod.rs | 6 +- .../crates/arbiter-server/src/grpc/client.rs | 180 +++++++++++++- .../arbiter-server/src/grpc/client/auth.rs | 7 +- .../arbiter-server/src/grpc/user_agent.rs | 225 +++++++++++++++++- 14 files changed, 605 insertions(+), 51 deletions(-) diff --git a/protobufs/client.proto b/protobufs/client.proto index c090a0d..126d99d 100644 --- a/protobufs/client.proto +++ b/protobufs/client.proto @@ -42,6 +42,7 @@ message ClientRequest { AuthChallengeRequest auth_challenge_request = 1; AuthChallengeSolution auth_challenge_solution = 2; google.protobuf.Empty query_vault_state = 3; + arbiter.evm.EvmSignTransactionRequest evm_sign_transaction = 5; } } diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto index fe41f87..79d8346 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/user_agent.proto @@ -137,6 +137,11 @@ message SdkClientConnectionResponse { message SdkClientConnectionCancel {} +message UserAgentEvmSignTransactionRequest { + int32 client_id = 1; + arbiter.evm.EvmSignTransactionRequest request = 2; +} + message UserAgentRequest { int32 id = 16; oneof payload { @@ -155,6 +160,7 @@ message UserAgentRequest { SdkClientRevokeRequest sdk_client_revoke = 13; google.protobuf.Empty sdk_client_list = 14; BootstrapEncryptedKey bootstrap_encrypted_key = 15; + UserAgentEvmSignTransactionRequest evm_sign_transaction = 17; } } message UserAgentResponse { @@ -175,5 +181,6 @@ message UserAgentResponse { SdkClientRevokeResponse sdk_client_revoke_response = 13; SdkClientListResponse sdk_client_list_response = 14; BootstrapResult bootstrap_result = 15; + arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 17; } } diff --git a/server/crates/arbiter-client/src/transport.rs b/server/crates/arbiter-client/src/transport.rs index d56a9f8..7332e89 100644 --- a/server/crates/arbiter-client/src/transport.rs +++ b/server/crates/arbiter-client/src/transport.rs @@ -1,6 +1,4 @@ -use arbiter_proto::proto::{ - client::{ClientRequest, ClientResponse}, -}; +use arbiter_proto::proto::client::{ClientRequest, ClientResponse}; use std::sync::atomic::{AtomicI32, Ordering}; use tokio::sync::mpsc; @@ -36,9 +34,7 @@ impl ClientTransport { .map_err(|_| ClientSignError::ChannelClosed) } - pub(crate) async fn recv( - &mut self, - ) -> std::result::Result { + pub(crate) async fn recv(&mut self) -> std::result::Result { match self.receiver.message().await { Ok(Some(resp)) => Ok(resp), Ok(None) => Err(ClientSignError::ConnectionClosed), diff --git a/server/crates/arbiter-client/src/wallets/evm.rs b/server/crates/arbiter-client/src/wallets/evm.rs index 32ae735..4533793 100644 --- a/server/crates/arbiter-client/src/wallets/evm.rs +++ b/server/crates/arbiter-client/src/wallets/evm.rs @@ -8,7 +8,15 @@ use async_trait::async_trait; use std::sync::Arc; use tokio::sync::Mutex; -use crate::transport::ClientTransport; +use arbiter_proto::proto::{ + client::{ + ClientRequest, client_request::Payload as ClientRequestPayload, + client_response::Payload as ClientResponsePayload, + }, + evm::evm_sign_transaction_response::Result as EvmSignTransactionResult, +}; + +use crate::transport::{ClientTransport, next_request_id}; pub struct ArbiterEvmWallet { transport: Arc>, @@ -79,11 +87,61 @@ impl TxSigner for ArbiterEvmWallet { &self, tx: &mut dyn SignableTransaction, ) -> Result { - let _transport = self.transport.lock().await; self.validate_chain_id(tx)?; - Err(Error::other( - "transaction signing is not supported by current arbiter.client protocol", - )) + let mut transport = self.transport.lock().await; + let request_id = next_request_id(); + let rlp_transaction = tx.encoded_for_signing(); + + transport + .send(ClientRequest { + request_id, + payload: Some(ClientRequestPayload::EvmSignTransaction( + arbiter_proto::proto::evm::EvmSignTransactionRequest { + wallet_address: self.address.to_vec(), + rlp_transaction, + }, + )), + }) + .await + .map_err(|_| Error::other("failed to send evm sign transaction request"))?; + + let response = transport + .recv() + .await + .map_err(|_| Error::other("failed to receive evm sign transaction response"))?; + + if response.request_id != Some(request_id) { + return Err(Error::other( + "received mismatched response id for evm sign transaction", + )); + } + + let payload = response + .payload + .ok_or_else(|| Error::other("missing evm sign transaction response payload"))?; + + let ClientResponsePayload::EvmSignTransaction(response) = payload else { + return Err(Error::other( + "unexpected response payload for evm sign transaction request", + )); + }; + + let result = response + .result + .ok_or_else(|| Error::other("missing evm sign transaction result"))?; + + match result { + EvmSignTransactionResult::Signature(signature) => { + Signature::try_from(signature.as_slice()) + .map_err(|_| Error::other("invalid signature returned by server")) + } + EvmSignTransactionResult::EvalError(eval_error) => Err(Error::other(format!( + "transaction rejected by policy: {eval_error:?}" + ))), + EvmSignTransactionResult::Error(code) => Err(Error::other(format!( + "server failed to sign transaction with error code {code}" + ))), + } } } diff --git a/server/crates/arbiter-server/src/actors/client/auth.rs b/server/crates/arbiter-server/src/actors/client/auth.rs index d957da8..bcf85d2 100644 --- a/server/crates/arbiter-server/src/actors/client/auth.rs +++ b/server/crates/arbiter-server/src/actors/client/auth.rs @@ -54,10 +54,19 @@ pub enum Outbound { AuthSuccess, } +#[derive(Debug, Clone)] +pub struct AuthenticatedClient { + pub pubkey: VerifyingKey, + pub client_id: i32, +} + /// Atomically reads and increments the nonce for a known client. /// Returns `None` if the pubkey is not registered. -async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result, Error> { - let pubkey_bytes = pubkey.as_bytes().to_vec(); +async fn get_nonce( + db: &db::DatabasePool, + pubkey: &VerifyingKey, +) -> Result, Error> { + let pubkey_bytes = pubkey.as_bytes(); let mut conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); @@ -65,7 +74,6 @@ async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result Result( props: &mut ClientConnection, transport: &mut T, -) -> Result +) -> Result where T: Bi> + Send + ?Sized, { - let Some(Inbound::AuthChallengeRequest { pubkey }) = transport.recv().await - else { + let Some(Inbound::AuthChallengeRequest { pubkey }) = transport.recv().await else { return Err(Error::Transport); }; - let nonce = match get_nonce(&props.db, &pubkey).await? { - Some(nonce) => nonce, + let (client_id, nonce) = match get_nonce(&props.db, &pubkey).await? { + Some(client_nonce) => client_nonce, None => { approve_new_client(&props.actors, pubkey).await?; match insert_client(&props.db, &pubkey).await? { - InsertClientResult::Inserted => 0, + InsertClientResult::Inserted => match get_nonce(&props.db, &pubkey).await? { + Some((client_id, _)) => (client_id, 0), + None => return Err(Error::DatabaseOperationFailed), + }, InsertClientResult::AlreadyExists => match get_nonce(&props.db, &pubkey).await? { - Some(nonce) => nonce, + Some((client_id, nonce)) => (client_id, nonce), None => return Err(Error::DatabaseOperationFailed), }, } @@ -245,5 +254,5 @@ where Error::Transport })?; - Ok(pubkey) + Ok(AuthenticatedClient { pubkey, client_id }) } diff --git a/server/crates/arbiter-server/src/actors/client/mod.rs b/server/crates/arbiter-server/src/actors/client/mod.rs index 3fae866..cf69ba7 100644 --- a/server/crates/arbiter-server/src/actors/client/mod.rs +++ b/server/crates/arbiter-server/src/actors/client/mod.rs @@ -10,11 +10,16 @@ use crate::{ pub struct ClientConnection { pub(crate) db: db::DatabasePool, pub(crate) actors: GlobalActors, + pub(crate) client_id: i32, } impl ClientConnection { pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self { - Self { db, actors } + Self { + db, + actors, + client_id: 0, + } } } @@ -26,7 +31,8 @@ where T: Bi> + Send + ?Sized, { match auth::authenticate(&mut props, transport).await { - Ok(_pubkey) => { + Ok(authenticated) => { + props.client_id = authenticated.client_id; ClientSession::spawn(ClientSession::new(props)); info!("Client authenticated, session started"); } diff --git a/server/crates/arbiter-server/src/actors/client/session.rs b/server/crates/arbiter-server/src/actors/client/session.rs index 93f2c6e..155c37d 100644 --- a/server/crates/arbiter-server/src/actors/client/session.rs +++ b/server/crates/arbiter-server/src/actors/client/session.rs @@ -1,11 +1,18 @@ use kameo::{Actor, messages}; use tracing::error; +use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature}; + use crate::{ actors::{ - GlobalActors, client::ClientConnection, keyholder::KeyHolderState, router::RegisterClient, + GlobalActors, + client::ClientConnection, + evm::{ClientSignTransaction, SignTransactionError}, + keyholder::KeyHolderState, + router::RegisterClient, }, db, + evm::VetError, }; pub struct ClientSession { @@ -34,6 +41,34 @@ impl ClientSession { Ok(vault_state) } + + #[message] + pub(crate) async fn handle_sign_transaction( + &mut self, + wallet_address: Address, + transaction: TxEip1559, + ) -> Result { + match self + .props + .actors + .evm + .ask(ClientSignTransaction { + client_id: self.props.client_id, + wallet_address, + transaction, + }) + .await + { + Ok(signature) => Ok(signature), + Err(kameo::error::SendError::HandlerError(SignTransactionError::Vet(vet_error))) => { + Err(SignTransactionRpcError::Vet(vet_error)) + } + Err(err) => { + error!(?err, "Failed to sign EVM transaction in client session"); + Err(SignTransactionRpcError::Internal) + } + } + } } impl Actor for ClientSession { @@ -69,3 +104,12 @@ pub enum Error { #[error("Internal error")] Internal, } + +#[derive(Debug, thiserror::Error)] +pub enum SignTransactionRpcError { + #[error("Policy evaluation failed")] + Vet(#[from] VetError), + + #[error("Internal error")] + Internal, +} diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index b13bfd9..e719b18 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -36,7 +36,10 @@ impl Error { pub struct UserAgentSession { props: UserAgentConnection, state: UserAgentStateMachine, - #[allow(dead_code, reason = "The session keeps ownership of the outbound transport even before the state-machine flow starts using it directly")] + #[allow( + dead_code, + reason = "The session keeps ownership of the outbound transport even before the state-machine flow starts using it directly" + )] sender: Box>, } @@ -44,8 +47,11 @@ mod connection; pub(crate) use connection::{ BootstrapError, HandleBootstrapEncryptedKey, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList, HandleQueryVaultState, + HandleSignTransaction, +}; +pub use connection::{ + HandleUnsealEncryptedKey, HandleUnsealRequest, SignTransactionError, UnsealError, }; -pub use connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}; impl UserAgentSession { pub(crate) fn new(props: UserAgentConnection, sender: Box>) -> Self { diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs index 44b47c3..49de59a 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs @@ -1,6 +1,6 @@ use std::sync::Mutex; -use alloy::primitives::Address; +use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature}; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use kameo::error::SendError; use kameo::messages; @@ -14,13 +14,14 @@ use crate::safe_cell::SafeCell; use crate::{ actors::{ evm::{ - Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants, + ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, + UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants, }, keyholder::{self, Bootstrap, TryUnseal}, user_agent::session::{ - UserAgentSession, - state::{UnsealContext, UserAgentEvents, UserAgentStates}, - }, + UserAgentSession, + state::{UnsealContext, UserAgentEvents, UserAgentStates}, + }, }, safe_cell::SafeCellHandle as _, }; @@ -103,6 +104,15 @@ pub enum BootstrapError { General(#[from] super::Error), } +#[derive(Debug, Error)] +pub enum SignTransactionError { + #[error("Policy evaluation failed")] + Vet(#[from] crate::evm::VetError), + + #[error("Internal signing error")] + Internal, +} + #[messages] impl UserAgentSession { #[message] @@ -351,4 +361,33 @@ impl UserAgentSession { } } } + + #[message] + pub(crate) async fn handle_sign_transaction( + &mut self, + client_id: i32, + wallet_address: Address, + transaction: TxEip1559, + ) -> Result { + match self + .props + .actors + .evm + .ask(ClientSignTransaction { + client_id, + wallet_address, + transaction, + }) + .await + { + Ok(signature) => Ok(signature), + Err(SendError::HandlerError(EvmSignError::Vet(vet_error))) => { + Err(SignTransactionError::Vet(vet_error)) + } + Err(err) => { + error!(?err, "EVM sign transaction failed in user-agent session"); + Err(SignTransactionError::Internal) + } + } + } } diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs index e77d994..2c43d05 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs @@ -36,8 +36,8 @@ use super::{DatabaseID, EvalContext, EvalViolation}; // Plain ether transfer #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Meaning { - to: Address, - value: U256, + pub(crate) to: Address, + pub(crate) value: U256, } impl Display for Meaning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs index 34378ed..21799ea 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs @@ -38,9 +38,9 @@ fn grant_join() -> _ { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Meaning { - token: &'static TokenInfo, - to: Address, - value: U256, + pub(crate) token: &'static TokenInfo, + pub(crate) to: Address, + pub(crate) value: U256, } impl std::fmt::Display for Meaning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/server/crates/arbiter-server/src/grpc/client.rs b/server/crates/arbiter-server/src/grpc/client.rs index 2fb1d24..f384092 100644 --- a/server/crates/arbiter-server/src/grpc/client.rs +++ b/server/crates/arbiter-server/src/grpc/client.rs @@ -1,4 +1,5 @@ use arbiter_proto::{ + google::protobuf::Empty as ProtoEmpty, proto::client::{ ClientRequest, ClientResponse, VaultState as ProtoVaultState, client_request::Payload as ClientRequestPayload, @@ -17,16 +18,135 @@ use crate::{ actors::{ client::{ self, ClientConnection, - session::{ClientSession, Error, HandleQueryVaultState}, + session::{ + ClientSession, Error, HandleQueryVaultState, HandleSignTransaction, + SignTransactionRpcError, + }, }, keyholder::KeyHolderState, }, + evm::{PolicyError, VetError, policies::EvalViolation}, grpc::request_tracker::RequestTracker, utils::defer, }; +use alloy::{ + consensus::TxEip1559, + primitives::{Address, U256}, + rlp::Decodable, +}; +use arbiter_proto::proto::evm::{ + EvmError as ProtoEvmError, EvmSignTransactionResponse, EvalViolation as ProtoEvalViolation, + GasLimitExceededViolation, NoMatchingGrantError, PolicyViolationsError, + SpecificMeaning as ProtoSpecificMeaning, TokenInfo as ProtoTokenInfo, + TransactionEvalError, + evm_sign_transaction_response::Result as EvmSignTransactionResult, + eval_violation::Kind as ProtoEvalViolationKind, + specific_meaning::Meaning as ProtoSpecificMeaningKind, + transaction_eval_error::Kind as ProtoTransactionEvalErrorKind, +}; + mod auth; +fn u256_to_proto_bytes(value: U256) -> Vec { + value.to_be_bytes::<32>().to_vec() +} + +fn meaning_to_proto(meaning: crate::evm::policies::SpecificMeaning) -> ProtoSpecificMeaning { + let kind = match meaning { + crate::evm::policies::SpecificMeaning::EtherTransfer(meaning) => { + ProtoSpecificMeaningKind::EtherTransfer(arbiter_proto::proto::evm::EtherTransferMeaning { + to: meaning.to.to_vec(), + value: u256_to_proto_bytes(meaning.value), + }) + } + crate::evm::policies::SpecificMeaning::TokenTransfer(meaning) => { + ProtoSpecificMeaningKind::TokenTransfer(arbiter_proto::proto::evm::TokenTransferMeaning { + token: Some(ProtoTokenInfo { + symbol: meaning.token.symbol.to_string(), + address: meaning.token.contract.to_vec(), + chain_id: meaning.token.chain, + }), + to: meaning.to.to_vec(), + value: u256_to_proto_bytes(meaning.value), + }) + } + }; + + ProtoSpecificMeaning { + meaning: Some(kind), + } +} + +fn violation_to_proto(violation: EvalViolation) -> ProtoEvalViolation { + let kind = match violation { + EvalViolation::InvalidTarget { target } => ProtoEvalViolationKind::InvalidTarget(target.to_vec()), + EvalViolation::GasLimitExceeded { + max_gas_fee_per_gas, + max_priority_fee_per_gas, + } => ProtoEvalViolationKind::GasLimitExceeded(GasLimitExceededViolation { + max_gas_fee_per_gas: max_gas_fee_per_gas.map(u256_to_proto_bytes), + max_priority_fee_per_gas: max_priority_fee_per_gas.map(u256_to_proto_bytes), + }), + EvalViolation::RateLimitExceeded => ProtoEvalViolationKind::RateLimitExceeded(ProtoEmpty {}), + EvalViolation::VolumetricLimitExceeded => { + ProtoEvalViolationKind::VolumetricLimitExceeded(ProtoEmpty {}) + } + EvalViolation::InvalidTime => ProtoEvalViolationKind::InvalidTime(ProtoEmpty {}), + EvalViolation::InvalidTransactionType => { + ProtoEvalViolationKind::InvalidTransactionType(ProtoEmpty {}) + } + }; + + ProtoEvalViolation { kind: Some(kind) } +} + +fn eval_error_to_proto(err: VetError) -> Option { + let kind = match err { + VetError::ContractCreationNotSupported => { + ProtoTransactionEvalErrorKind::ContractCreationNotSupported(ProtoEmpty {}) + } + VetError::UnsupportedTransactionType => { + ProtoTransactionEvalErrorKind::UnsupportedTransactionType(ProtoEmpty {}) + } + VetError::Evaluated(meaning, policy_error) => match policy_error { + PolicyError::NoMatchingGrant => { + ProtoTransactionEvalErrorKind::NoMatchingGrant(NoMatchingGrantError { + meaning: Some(meaning_to_proto(meaning)), + }) + } + PolicyError::Violations(violations) => { + ProtoTransactionEvalErrorKind::PolicyViolations(PolicyViolationsError { + meaning: Some(meaning_to_proto(meaning)), + violations: violations.into_iter().map(violation_to_proto).collect(), + }) + } + PolicyError::Pool(_) | PolicyError::Database(_) => { + return None; + } + }, + }; + + Some(TransactionEvalError { kind: Some(kind) }) +} + +fn decode_eip1559_transaction(payload: &[u8]) -> Result { + let mut body = payload; + if let Some((prefix, rest)) = payload.split_first() + && *prefix == 0x02 + { + body = rest; + } + + let mut cursor = body; + let transaction = TxEip1559::decode(&mut cursor).map_err(|_| ())?; + if !cursor.is_empty() { + return Err(()); + } + + Ok(transaction) +} + async fn dispatch_loop( mut bi: GrpcBi, actor: ActorRef, @@ -90,6 +210,64 @@ async fn dispatch_conn_message( } .into(), ), + ClientRequestPayload::EvmSignTransaction(request) => { + let wallet_address = match <[u8; 20]>::try_from(request.wallet_address.as_slice()) { + Ok(address) => Address::from(address), + Err(_) => { + let _ = bi + .send(Err(Status::invalid_argument("Invalid EVM wallet address"))) + .await; + return Err(()); + } + }; + + let transaction = match decode_eip1559_transaction(&request.rlp_transaction) { + Ok(transaction) => transaction, + Err(()) => { + let _ = bi + .send(Err(Status::invalid_argument( + "Invalid EIP-1559 RLP transaction", + ))) + .await; + return Err(()); + } + }; + + let response = match actor + .ask(HandleSignTransaction { + wallet_address, + transaction, + }) + .await + { + Ok(signature) => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Signature(signature.as_bytes().to_vec())), + }, + Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Vet(vet_error))) => { + match eval_error_to_proto(vet_error) { + Some(eval_error) => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::EvalError(eval_error)), + }, + None => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error(ProtoEvmError::Internal.into())), + }, + } + } + Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Internal)) => { + EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error(ProtoEvmError::Internal.into())), + } + } + Err(err) => { + warn!(error = ?err, "Failed to sign EVM transaction"); + EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error(ProtoEvmError::Internal.into())), + } + } + }; + + ClientResponsePayload::EvmSignTransaction(response) + } payload => { warn!(?payload, "Unsupported post-auth client request"); let _ = bi diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 49d8d55..8427efe 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -151,7 +151,9 @@ impl Receiver for AuthTransportAdapter<'_> { _ => { let _ = self .bi - .send(Err(Status::invalid_argument("Unsupported client auth request"))) + .send(Err(Status::invalid_argument( + "Unsupported client auth request", + ))) .await; None } @@ -168,6 +170,7 @@ pub async fn start( response_id: &mut Option, ) -> Result<(), auth::Error> { let mut transport = AuthTransportAdapter::new(bi, request_tracker, response_id); - client::auth::authenticate(conn, &mut transport).await?; + let authenticated = client::auth::authenticate(conn, &mut transport).await?; + conn.client_id = authenticated.client_id; Ok(()) } diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index 674471c..81cc863 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -4,17 +4,24 @@ use arbiter_proto::{ google::protobuf::{Empty as ProtoEmpty, Timestamp as ProtoTimestamp}, proto::{ evm::{ - EtherTransferSettings as ProtoEtherTransferSettings, EvmError as ProtoEvmError, - EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest, - EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry, + EtherTransferSettings as ProtoEtherTransferSettings, + EvalViolation as ProtoEvalViolation, EvmError as ProtoEvmError, EvmGrantCreateRequest, + EvmGrantCreateResponse, EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, + EvmGrantListResponse, EvmSignTransactionResponse, GasLimitExceededViolation, + GrantEntry, NoMatchingGrantError, PolicyViolationsError, SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant, - TokenTransferSettings as ProtoTokenTransferSettings, + SpecificMeaning as ProtoSpecificMeaning, TokenInfo as ProtoTokenInfo, + TokenTransferSettings as ProtoTokenTransferSettings, TransactionEvalError, TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList, - WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult, + WalletListResponse, eval_violation::Kind as ProtoEvalViolationKind, + evm_grant_create_response::Result as EvmGrantCreateResult, evm_grant_delete_response::Result as EvmGrantDeleteResult, evm_grant_list_response::Result as EvmGrantListResult, + evm_sign_transaction_response::Result as EvmSignTransactionResult, specific_grant::Grant as ProtoSpecificGrantType, + specific_meaning::Meaning as ProtoSpecificMeaningKind, + transaction_eval_error::Kind as ProtoTransactionEvalErrorKind, wallet_create_response::Result as WalletCreateResult, wallet_list_response::Result as WalletListResult, }, @@ -23,8 +30,8 @@ use arbiter_proto::{ BootstrapResult as ProtoBootstrapResult, SdkClientConnectionResponse as ProtoSdkClientConnectionResponse, UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult, - UnsealStart, UserAgentRequest, UserAgentResponse, VaultState as ProtoVaultState, - user_agent_request::Payload as UserAgentRequestPayload, + UnsealStart, UserAgentEvmSignTransactionRequest, UserAgentRequest, UserAgentResponse, + VaultState as ProtoVaultState, user_agent_request::Payload as UserAgentRequestPayload, user_agent_response::Payload as UserAgentResponsePayload, }, }, @@ -47,7 +54,9 @@ use crate::{ session::{ BootstrapError, Error, HandleBootstrapEncryptedKey, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList, - HandleQueryVaultState, HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError, + HandleQueryVaultState, HandleSignTransaction, HandleUnsealEncryptedKey, + HandleUnsealRequest, SignTransactionError as SessionSignTransactionError, + UnsealError, }, }, }, @@ -55,12 +64,124 @@ use crate::{ Grant, SharedGrantSettings, SpecificGrant, TransactionRateLimit, VolumeRateLimit, ether_transfer, token_transfers, }, + evm::{PolicyError, VetError, policies::EvalViolation}, grpc::request_tracker::RequestTracker, utils::defer, }; -use alloy::primitives::{Address, U256}; +use alloy::{ + consensus::TxEip1559, + primitives::{Address, U256}, + rlp::Decodable, +}; mod auth; +fn u256_to_proto_bytes(value: U256) -> Vec { + value.to_be_bytes::<32>().to_vec() +} + +fn meaning_to_proto(meaning: crate::evm::policies::SpecificMeaning) -> ProtoSpecificMeaning { + let kind = match meaning { + crate::evm::policies::SpecificMeaning::EtherTransfer(meaning) => { + ProtoSpecificMeaningKind::EtherTransfer( + arbiter_proto::proto::evm::EtherTransferMeaning { + to: meaning.to.to_vec(), + value: u256_to_proto_bytes(meaning.value), + }, + ) + } + crate::evm::policies::SpecificMeaning::TokenTransfer(meaning) => { + ProtoSpecificMeaningKind::TokenTransfer( + arbiter_proto::proto::evm::TokenTransferMeaning { + token: Some(ProtoTokenInfo { + symbol: meaning.token.symbol.to_string(), + address: meaning.token.contract.to_vec(), + chain_id: meaning.token.chain, + }), + to: meaning.to.to_vec(), + value: u256_to_proto_bytes(meaning.value), + }, + ) + } + }; + + ProtoSpecificMeaning { + meaning: Some(kind), + } +} + +fn violation_to_proto(violation: EvalViolation) -> ProtoEvalViolation { + let kind = match violation { + EvalViolation::InvalidTarget { target } => { + ProtoEvalViolationKind::InvalidTarget(target.to_vec()) + } + EvalViolation::GasLimitExceeded { + max_gas_fee_per_gas, + max_priority_fee_per_gas, + } => ProtoEvalViolationKind::GasLimitExceeded(GasLimitExceededViolation { + max_gas_fee_per_gas: max_gas_fee_per_gas.map(u256_to_proto_bytes), + max_priority_fee_per_gas: max_priority_fee_per_gas.map(u256_to_proto_bytes), + }), + EvalViolation::RateLimitExceeded => { + ProtoEvalViolationKind::RateLimitExceeded(ProtoEmpty {}) + } + EvalViolation::VolumetricLimitExceeded => { + ProtoEvalViolationKind::VolumetricLimitExceeded(ProtoEmpty {}) + } + EvalViolation::InvalidTime => ProtoEvalViolationKind::InvalidTime(ProtoEmpty {}), + EvalViolation::InvalidTransactionType => { + ProtoEvalViolationKind::InvalidTransactionType(ProtoEmpty {}) + } + }; + + ProtoEvalViolation { kind: Some(kind) } +} + +fn eval_error_to_proto(err: VetError) -> Option { + let kind = match err { + VetError::ContractCreationNotSupported => { + ProtoTransactionEvalErrorKind::ContractCreationNotSupported(ProtoEmpty {}) + } + VetError::UnsupportedTransactionType => { + ProtoTransactionEvalErrorKind::UnsupportedTransactionType(ProtoEmpty {}) + } + VetError::Evaluated(meaning, policy_error) => match policy_error { + PolicyError::NoMatchingGrant => { + ProtoTransactionEvalErrorKind::NoMatchingGrant(NoMatchingGrantError { + meaning: Some(meaning_to_proto(meaning)), + }) + } + PolicyError::Violations(violations) => { + ProtoTransactionEvalErrorKind::PolicyViolations(PolicyViolationsError { + meaning: Some(meaning_to_proto(meaning)), + violations: violations.into_iter().map(violation_to_proto).collect(), + }) + } + PolicyError::Pool(_) | PolicyError::Database(_) => { + return None; + } + }, + }; + + Some(TransactionEvalError { kind: Some(kind) }) +} + +fn decode_eip1559_transaction(payload: &[u8]) -> Result { + let mut body = payload; + if let Some((prefix, rest)) = payload.split_first() + && *prefix == 0x02 + { + body = rest; + } + + let mut cursor = body; + let transaction = TxEip1559::decode(&mut cursor).map_err(|_| ())?; + if !cursor.is_empty() { + return Err(()); + } + + Ok(transaction) +} + pub struct OutOfBandAdapter(mpsc::Sender); #[async_trait] @@ -271,6 +392,92 @@ async fn dispatch_conn_message( actor.ask(HandleGrantDelete { grant_id }).await, )) } + UserAgentRequestPayload::EvmSignTransaction(UserAgentEvmSignTransactionRequest { + client_id, + request, + }) => { + if client_id <= 0 { + let _ = bi + .send(Err(Status::invalid_argument("Invalid SDK client id"))) + .await; + return Err(()); + } + + let Some(request) = request else { + let _ = bi + .send(Err(Status::invalid_argument( + "Missing EVM sign transaction payload", + ))) + .await; + return Err(()); + }; + + let wallet_address = match <[u8; 20]>::try_from(request.wallet_address.as_slice()) { + Ok(address) => Address::from(address), + Err(_) => { + let _ = bi + .send(Err(Status::invalid_argument("Invalid EVM wallet address"))) + .await; + return Err(()); + } + }; + + let transaction = match decode_eip1559_transaction(&request.rlp_transaction) { + Ok(transaction) => transaction, + Err(()) => { + let _ = bi + .send(Err(Status::invalid_argument( + "Invalid EIP-1559 RLP transaction", + ))) + .await; + return Err(()); + } + }; + + let response = match actor + .ask(HandleSignTransaction { + client_id, + wallet_address, + transaction, + }) + .await + { + Ok(signature) => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Signature( + signature.as_bytes().to_vec(), + )), + }, + Err(SendError::HandlerError(SessionSignTransactionError::Vet(vet_error))) => { + match eval_error_to_proto(vet_error) { + Some(eval_error) => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::EvalError(eval_error)), + }, + None => EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error( + ProtoEvmError::Internal.into(), + )), + }, + } + } + Err(SendError::HandlerError(SessionSignTransactionError::Internal)) => { + EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error( + ProtoEvmError::Internal.into(), + )), + } + } + Err(err) => { + warn!(error = ?err, "Failed to sign EVM transaction via user-agent"); + EvmSignTransactionResponse { + result: Some(EvmSignTransactionResult::Error( + ProtoEvmError::Internal.into(), + )), + } + } + }; + + UserAgentResponsePayload::EvmSignTransaction(response) + } payload => { warn!(?payload, "Unsupported post-auth user agent request"); let _ = bi From 0388fa2c8b0a98fba654517c11b0ecd0107c40c6 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Sun, 29 Mar 2026 22:54:12 +0200 Subject: [PATCH 2/5] fix(server): enforce volumetric cap using past + current transfer value --- .../src/evm/policies/ether_transfer/mod.rs | 9 +++++---- .../src/evm/policies/ether_transfer/tests.rs | 8 ++++---- .../src/evm/policies/token_transfers/mod.rs | 9 +++++---- .../src/evm/policies/token_transfers/tests.rs | 10 +++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs index b6c68c6..e823f07 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/mod.rs @@ -91,6 +91,7 @@ async fn query_relevant_past_transaction( async fn check_rate_limits( grant: &Grant, + current_transfer_value: U256, db: &mut impl AsyncConnection, ) -> QueryResult> { let mut violations = Vec::new(); @@ -99,12 +100,12 @@ async fn check_rate_limits( let past_transaction = query_relevant_past_transaction(grant.id, window, db).await?; let window_start = chrono::Utc::now() - grant.settings.limit.window; - let cumulative_volume: U256 = past_transaction + let prospective_cumulative_volume: U256 = past_transaction .iter() .filter(|(_, timestamp)| timestamp >= &window_start) - .fold(U256::default(), |acc, (value, _)| acc + *value); + .fold(current_transfer_value, |acc, (value, _)| acc + *value); - if cumulative_volume > grant.settings.limit.max_volume { + if prospective_cumulative_volume > grant.settings.limit.max_volume { violations.push(EvalViolation::VolumetricLimitExceeded); } @@ -141,7 +142,7 @@ impl Policy for EtherTransfer { violations.push(EvalViolation::InvalidTarget { target: meaning.to }); } - let rate_violations = check_rate_limits(grant, db).await?; + let rate_violations = check_rate_limits(grant, meaning.value, db).await?; violations.extend(rate_violations); Ok(violations) diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs index cba78b0..9ba48be 100644 --- a/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer/tests.rs @@ -198,7 +198,7 @@ async fn evaluate_rejects_volume_over_limit() { grant_id, wallet_access_id: WALLET_ACCESS_ID, chain_id: CHAIN_ID as i32, - eth_value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(), + eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(), signed_at: SqliteTimestamp(Utc::now()), }) .execute(&mut *conn) @@ -211,7 +211,7 @@ async fn evaluate_rejects_volume_over_limit() { shared: shared(), settings, }; - let context = ctx(ALLOWED, U256::from(100u64)); + let context = ctx(ALLOWED, U256::from(1u64)); let m = EtherTransfer::analyze(&context).unwrap(); let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn) .await @@ -233,13 +233,13 @@ async fn evaluate_passes_at_exactly_volume_limit() { .await .unwrap(); - // Exactly at the limit — the check is `>`, so this should not violate + // Exactly at the limit including current transfer — check is `>`, so this should not violate insert_into(evm_transaction_log::table) .values(NewEvmTransactionLog { grant_id, wallet_access_id: WALLET_ACCESS_ID, chain_id: CHAIN_ID as i32, - eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(), + eth_value: utils::u256_to_bytes(U256::from(900u64)).to_vec(), signed_at: SqliteTimestamp(Utc::now()), }) .execute(&mut *conn) diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs index bfd8ba2..7dfec70 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/mod.rs @@ -101,6 +101,7 @@ async fn query_relevant_past_transfers( async fn check_volume_rate_limits( grant: &Grant, + current_transfer_value: U256, db: &mut impl AsyncConnection, ) -> QueryResult> { let mut violations = Vec::new(); @@ -113,12 +114,12 @@ async fn check_volume_rate_limits( for limit in &grant.settings.volume_limits { let window_start = chrono::Utc::now() - limit.window; - let cumulative_volume: U256 = past_transfers + let prospective_cumulative_volume: U256 = past_transfers .iter() .filter(|(_, timestamp)| timestamp >= &window_start) - .fold(U256::default(), |acc, (value, _)| acc + *value); + .fold(current_transfer_value, |acc, (value, _)| acc + *value); - if cumulative_volume > limit.max_volume { + if prospective_cumulative_volume > limit.max_volume { violations.push(EvalViolation::VolumetricLimitExceeded); break; } @@ -163,7 +164,7 @@ impl Policy for TokenTransfer { violations.push(EvalViolation::InvalidTarget { target: meaning.to }); } - let rate_violations = check_volume_rate_limits(grant, db).await?; + let rate_violations = check_volume_rate_limits(grant, meaning.value, db).await?; violations.extend(rate_violations); Ok(violations) diff --git a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs index d8a5947..2f1b72f 100644 --- a/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs +++ b/server/crates/arbiter-server/src/evm/policies/token_transfers/tests.rs @@ -220,7 +220,7 @@ async fn evaluate_rejects_wrong_restricted_recipient() { } #[tokio::test] -async fn evaluate_passes_volume_within_limit() { +async fn evaluate_passes_volume_at_exact_limit() { let db = db::create_test_pool().await; let mut conn = db.get().await.unwrap(); @@ -230,7 +230,7 @@ async fn evaluate_passes_volume_within_limit() { .await .unwrap(); - // Record a past transfer of 500 (within 1000 limit) + // Record a past transfer of 900, with current transfer 100 => exactly 1000 limit use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log}; insert_into(evm_token_transfer_log::table) .values(NewEvmTokenTransferLog { @@ -239,7 +239,7 @@ async fn evaluate_passes_volume_within_limit() { chain_id: CHAIN_ID as i32, token_contract: DAI.to_vec(), recipient_address: RECIPIENT.to_vec(), - value: utils::u256_to_bytes(U256::from(500u64)).to_vec(), + value: utils::u256_to_bytes(U256::from(900u64)).to_vec(), }) .execute(&mut *conn) .await @@ -282,7 +282,7 @@ async fn evaluate_rejects_volume_over_limit() { chain_id: CHAIN_ID as i32, token_contract: DAI.to_vec(), recipient_address: RECIPIENT.to_vec(), - value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(), + value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(), }) .execute(&mut *conn) .await @@ -294,7 +294,7 @@ async fn evaluate_rejects_volume_over_limit() { shared: shared(), settings, }; - let calldata = transfer_calldata(RECIPIENT, U256::from(100u64)); + let calldata = transfer_calldata(RECIPIENT, U256::from(1u64)); let context = ctx(DAI, calldata); let m = TokenTransfer::analyze(&context).unwrap(); let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn) From 8feda7990c90c501cefa7044c0a040189ba4c2f6 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Sun, 29 Mar 2026 23:05:38 +0200 Subject: [PATCH 3/5] fix(auth): reject invalid challenge signatures instead of transitioning to AuthOk --- .../src/actors/user_agent/auth/state.rs | 13 ++-- .../arbiter-server/tests/user_agent/auth.rs | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs index c422589..eea0661 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/auth/state.rs @@ -210,13 +210,16 @@ where } }; - if valid { - self.transport - .send(Ok(Outbound::AuthSuccess)) - .await - .map_err(|_| Error::Transport)?; + if !valid { + error!("Invalid challenge solution signature"); + return Err(Error::InvalidChallengeSolution); } + self.transport + .send(Ok(Outbound::AuthSuccess)) + .await + .map_err(|_| Error::Transport)?; + Ok(key.clone()) } } diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/user_agent/auth.rs index 285ddcf..3fd8c92 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/user_agent/auth.rs @@ -165,3 +165,69 @@ pub async fn test_challenge_auth() { task.await.unwrap().unwrap(); } + +#[tokio::test] +#[test_log::test] +pub async fn test_challenge_auth_rejects_invalid_signature() { + let db = db::create_test_pool().await; + let actors = GlobalActors::spawn(db.clone()).await.unwrap(); + + let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng()); + let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec(); + + // Pre-register key with key_type + { + 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), + )) + .execute(&mut conn) + .await + .unwrap(); + } + + let (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 + }); + + test_transport + .send(auth::Inbound::AuthChallengeRequest { + pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()), + bootstrap_token: None, + }) + .await + .unwrap(); + + let response = test_transport + .recv() + .await + .expect("should receive challenge"); + let challenge = match response { + Ok(resp) => match resp { + auth::Outbound::AuthChallenge { nonce } => nonce, + other => panic!("Expected AuthChallenge, got {other:?}"), + }, + Err(err) => panic!("Expected Ok response, got Err({err:?})"), + }; + + // Sign a different challenge value so signature format is valid but verification must fail. + let wrong_challenge = arbiter_proto::format_challenge(challenge + 1, &pubkey_bytes); + let signature = new_key.sign(&wrong_challenge); + + test_transport + .send(auth::Inbound::AuthChallengeSolution { + signature: signature.to_bytes().to_vec(), + }) + .await + .unwrap(); + + assert!(matches!( + task.await.unwrap(), + Err(auth::Error::InvalidChallengeSolution) + )); +} From 63a4875fdb54b2ee8e4aceffcfbd323118dd70de Mon Sep 17 00:00:00 2001 From: CleverWild Date: Sun, 29 Mar 2026 23:16:37 +0200 Subject: [PATCH 4/5] fix(keyholder): remove dead overwritten select in try_unseal query --- server/crates/arbiter-server/src/actors/keyholder/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/crates/arbiter-server/src/actors/keyholder/mod.rs b/server/crates/arbiter-server/src/actors/keyholder/mod.rs index 3a245af..fbf3d50 100644 --- a/server/crates/arbiter-server/src/actors/keyholder/mod.rs +++ b/server/crates/arbiter-server/src/actors/keyholder/mod.rs @@ -214,7 +214,6 @@ impl KeyHolder { let mut conn = self.db.get().await?; schema::root_key_history::table .filter(schema::root_key_history::id.eq(*root_key_history_id)) - .select(schema::root_key_history::data_encryption_nonce) .select(RootKeyHistory::as_select()) .first(&mut conn) .await? From 5bce9fd68ec70e4bc0957bebb4594643efe962ec Mon Sep 17 00:00:00 2001 From: CleverWild Date: Sun, 29 Mar 2026 19:07:12 +0200 Subject: [PATCH 5/5] chore: bump mise deps --- mise.lock | 62 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/mise.lock b/mise.lock index 3cd025e..9cf1bee 100644 --- a/mise.lock +++ b/mise.lock @@ -8,10 +8,18 @@ backend = "aqua:ast-grep/ast-grep" checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836" url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip" +[tools.ast-grep."platforms.linux-arm64-musl"] +checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip" + [tools.ast-grep."platforms.linux-x64"] checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651" url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip" +[tools.ast-grep."platforms.linux-x64-musl"] +checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip" + [tools.ast-grep."platforms.macos-arm64"] checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620" url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip" @@ -32,10 +40,6 @@ backend = "cargo:cargo-audit" version = "0.13.9" backend = "cargo:cargo-edit" -[[tools."cargo:cargo-features"]] -version = "1.0.0" -backend = "cargo:cargo-features" - [[tools."cargo:cargo-features-manager"]] version = "0.11.1" backend = "cargo:cargo-features-manager" @@ -49,21 +53,13 @@ version = "0.9.126" backend = "cargo:cargo-nextest" [[tools."cargo:cargo-shear"]] -version = "1.9.1" +version = "1.11.2" backend = "cargo:cargo-shear" [[tools."cargo:cargo-vet"]] version = "0.10.2" backend = "cargo:cargo-vet" -[[tools."cargo:diesel-cli"]] -version = "2.3.6" -backend = "cargo:diesel-cli" - -[tools."cargo:diesel-cli".options] -default-features = "false" -features = "sqlite,sqlite-bundled" - [[tools."cargo:diesel_cli"]] version = "2.3.6" backend = "cargo:diesel_cli" @@ -72,10 +68,6 @@ backend = "cargo:diesel_cli" default-features = "false" features = "sqlite,sqlite-bundled" -[[tools."cargo:rinf_cli"]] -version = "8.9.1" -backend = "cargo:rinf_cli" - [[tools.flutter]] version = "3.38.9-stable" backend = "asdf:flutter" @@ -88,10 +80,18 @@ backend = "aqua:protocolbuffers/protobuf/protoc" checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip" +[tools.protoc."platforms.linux-arm64-musl"] +checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip" + [tools.protoc."platforms.linux-x64"] checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip" +[tools.protoc."platforms.linux-x64-musl"] +checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip" + [tools.protoc."platforms.macos-arm64"] checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed" url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip" @@ -109,24 +109,32 @@ version = "3.14.3" backend = "core:python" [tools.python."platforms.linux-arm64"] -checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-arm64-musl"] +checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" [tools.python."platforms.linux-x64"] -checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64-musl"] +checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" [tools.python."platforms.macos-arm64"] -checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz" +checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz" [tools.python."platforms.macos-x64"] -checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz" +checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz" [tools.python."platforms.windows-x64"] -checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" +checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" [[tools.rust]] version = "1.93.0"