Compare commits
9 Commits
77c3babec7
...
784261f4d8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784261f4d8 | ||
|
|
971db0e919 | ||
|
|
e1a8553142 | ||
|
|
ec70561c93 | ||
|
|
3993d3a8cc | ||
|
|
c87456ae2f | ||
|
|
e89983de3a | ||
|
|
f56668d9f6 | ||
|
|
434738bae5 |
@@ -106,15 +106,15 @@ enum VaultState {
|
|||||||
VAULT_STATE_ERROR = 4;
|
VAULT_STATE_ERROR = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientConnectionRequest {
|
message SdkClientConnectionRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientConnectionResponse {
|
message SdkClientConnectionResponse {
|
||||||
bool approved = 1;
|
bool approved = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientConnectionCancel {}
|
message SdkClientConnectionCancel {}
|
||||||
|
|
||||||
message UserAgentRequest {
|
message UserAgentRequest {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
@@ -128,7 +128,7 @@ message UserAgentRequest {
|
|||||||
arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8;
|
arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8;
|
||||||
arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9;
|
arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9;
|
||||||
arbiter.evm.EvmGrantListRequest evm_grant_list = 10;
|
arbiter.evm.EvmGrantListRequest evm_grant_list = 10;
|
||||||
ClientConnectionResponse client_connection_response = 11;
|
SdkClientConnectionResponse sdk_client_connection_response = 11;
|
||||||
SdkClientApproveRequest sdk_client_approve = 12;
|
SdkClientApproveRequest sdk_client_approve = 12;
|
||||||
SdkClientRevokeRequest sdk_client_revoke = 13;
|
SdkClientRevokeRequest sdk_client_revoke = 13;
|
||||||
google.protobuf.Empty sdk_client_list = 14;
|
google.protobuf.Empty sdk_client_list = 14;
|
||||||
@@ -146,8 +146,8 @@ message UserAgentResponse {
|
|||||||
arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8;
|
arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8;
|
||||||
arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9;
|
arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9;
|
||||||
arbiter.evm.EvmGrantListResponse evm_grant_list = 10;
|
arbiter.evm.EvmGrantListResponse evm_grant_list = 10;
|
||||||
ClientConnectionRequest client_connection_request = 11;
|
SdkClientConnectionRequest sdk_client_connection_request = 11;
|
||||||
ClientConnectionCancel client_connection_cancel = 12;
|
SdkClientConnectionCancel sdk_client_connection_cancel = 12;
|
||||||
SdkClientApproveResponse sdk_client_approve = 13;
|
SdkClientApproveResponse sdk_client_approve = 13;
|
||||||
SdkClientRevokeResponse sdk_client_revoke = 14;
|
SdkClientRevokeResponse sdk_client_revoke = 14;
|
||||||
SdkClientListResponse sdk_client_list = 15;
|
SdkClientListResponse sdk_client_list = 15;
|
||||||
|
|||||||
139
server/Cargo.lock
generated
139
server/Cargo.lock
generated
@@ -100,7 +100,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -136,7 +136,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -178,7 +178,7 @@ dependencies = [
|
|||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
"crc",
|
"crc",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -203,7 +203,7 @@ dependencies = [
|
|||||||
"alloy-rlp",
|
"alloy-rlp",
|
||||||
"borsh",
|
"borsh",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -239,7 +239,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -280,7 +280,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ dependencies = [
|
|||||||
"futures-utils-wasm",
|
"futures-utils-wasm",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -382,7 +382,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
@@ -475,7 +475,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -501,7 +501,7 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"k256",
|
"k256",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -517,7 +517,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"k256",
|
"k256",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -608,7 +608,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -644,7 +644,7 @@ dependencies = [
|
|||||||
"nybbles",
|
"nybbles",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -684,8 +684,9 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"http",
|
"http",
|
||||||
|
"rand 0.10.0",
|
||||||
"rustls-webpki",
|
"rustls-webpki",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
@@ -708,7 +709,7 @@ dependencies = [
|
|||||||
"rcgen",
|
"rcgen",
|
||||||
"rstest",
|
"rstest",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tonic",
|
"tonic",
|
||||||
"tonic-prost",
|
"tonic-prost",
|
||||||
@@ -733,6 +734,7 @@ dependencies = [
|
|||||||
"diesel-async",
|
"diesel-async",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"fatality",
|
||||||
"futures",
|
"futures",
|
||||||
"insta",
|
"insta",
|
||||||
"k256",
|
"k256",
|
||||||
@@ -751,7 +753,7 @@ dependencies = [
|
|||||||
"spki",
|
"spki",
|
||||||
"strum",
|
"strum",
|
||||||
"test-log",
|
"test-log",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
@@ -761,13 +763,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arbiter-terrors-poc"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"terrors",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbiter-tokens-registry"
|
name = "arbiter-tokens-registry"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -791,7 +786,7 @@ dependencies = [
|
|||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"smlang",
|
"smlang",
|
||||||
"spki",
|
"spki",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
@@ -1019,7 +1014,7 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2079,6 +2074,21 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "expander"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2"
|
||||||
|
dependencies = [
|
||||||
|
"blake2",
|
||||||
|
"file-guard",
|
||||||
|
"fs-err",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -2107,6 +2117,30 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fatality"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec6f82451ff7f0568c6181287189126d492b5654e30a788add08027b6363d019"
|
||||||
|
dependencies = [
|
||||||
|
"fatality-proc-macro",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fatality-proc-macro"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303"
|
||||||
|
dependencies = [
|
||||||
|
"expander",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -2129,6 +2163,16 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
|
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "file-guard"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -2190,6 +2234,15 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs-err"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs_extra"
|
name = "fs_extra"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -3784,7 +3837,7 @@ dependencies = [
|
|||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -3805,7 +3858,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -4140,7 +4193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
|
checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4863,12 +4916,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "terrors"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "987fd8c678ca950df2a18b2c6e9da6ca511d449278fab3565efe0d49c0c07a5d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test-log"
|
name = "test-log"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -4900,13 +4947,33 @@ dependencies = [
|
|||||||
"unicode-width 0.2.2",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"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 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6000,7 +6067,7 @@ dependencies = [
|
|||||||
"nom",
|
"nom",
|
||||||
"oid-registry",
|
"oid-registry",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ thiserror.workspace = true
|
|||||||
http = "1.4.0"
|
http = "1.4.0"
|
||||||
rustls-webpki = { version = "0.103.9", features = ["aws-lc-rs"] }
|
rustls-webpki = { version = "0.103.9", features = ["aws-lc-rs"] }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use alloy::{
|
|||||||
signers::{Error, Result, Signer},
|
signers::{Error, Result, Signer},
|
||||||
};
|
};
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
format_challenge,
|
format_challenge, home_path,
|
||||||
proto::{
|
proto::{
|
||||||
arbiter_service_client::ArbiterServiceClient,
|
arbiter_service_client::ArbiterServiceClient,
|
||||||
client::{
|
client::{
|
||||||
@@ -21,10 +21,13 @@ use arbiter_proto::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ed25519_dalek::Signer as _;
|
use ed25519_dalek::Signer as _;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tonic::transport::ClientTlsConfig;
|
use tonic::transport::ClientTlsConfig;
|
||||||
|
|
||||||
|
const BUFFER_LENGTH: usize = 16;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ConnectError {
|
pub enum ConnectError {
|
||||||
#[error("Could not establish connection")]
|
#[error("Could not establish connection")]
|
||||||
@@ -50,6 +53,83 @@ pub enum ConnectError {
|
|||||||
|
|
||||||
#[error("Unexpected auth response payload")]
|
#[error("Unexpected auth response payload")]
|
||||||
UnexpectedAuthResponse,
|
UnexpectedAuthResponse,
|
||||||
|
|
||||||
|
#[error("Signing key storage error")]
|
||||||
|
Storage(#[from] StorageError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum StorageError {
|
||||||
|
#[error("I/O error")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Invalid signing key length in storage: expected {expected} bytes, got {actual} bytes")]
|
||||||
|
InvalidKeyLength { expected: usize, actual: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SigningKeyStorage {
|
||||||
|
fn load_or_create(&self) -> std::result::Result<ed25519_dalek::SigningKey, StorageError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileSigningKeyStorage {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSigningKeyStorage {
|
||||||
|
pub const DEFAULT_FILE_NAME: &str = "sdk_client_ed25519.key";
|
||||||
|
|
||||||
|
pub fn new(path: impl Into<PathBuf>) -> Self {
|
||||||
|
Self { path: path.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_default_location() -> std::result::Result<Self, StorageError> {
|
||||||
|
Ok(Self::new(home_path()?.join(Self::DEFAULT_FILE_NAME)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_key(path: &Path) -> std::result::Result<ed25519_dalek::SigningKey, StorageError> {
|
||||||
|
let bytes = std::fs::read(path)?;
|
||||||
|
let raw: [u8; 32] =
|
||||||
|
bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|v: Vec<u8>| StorageError::InvalidKeyLength {
|
||||||
|
expected: 32,
|
||||||
|
actual: v.len(),
|
||||||
|
})?;
|
||||||
|
Ok(ed25519_dalek::SigningKey::from_bytes(&raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SigningKeyStorage for FileSigningKeyStorage {
|
||||||
|
fn load_or_create(&self) -> std::result::Result<ed25519_dalek::SigningKey, StorageError> {
|
||||||
|
if let Some(parent) = self.path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.path.exists() {
|
||||||
|
return Self::read_key(&self.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
|
let raw_key = key.to_bytes();
|
||||||
|
|
||||||
|
// Use create_new to prevent accidental overwrite if another process creates the key first.
|
||||||
|
match std::fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&self.path)
|
||||||
|
{
|
||||||
|
Ok(mut file) => {
|
||||||
|
use std::io::Write as _;
|
||||||
|
file.write_all(&raw_key)?;
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||||
|
Self::read_key(&self.path)
|
||||||
|
}
|
||||||
|
Err(err) => Err(StorageError::Io(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@@ -65,6 +145,9 @@ enum ClientSignError {
|
|||||||
|
|
||||||
#[error("Remote signing was rejected")]
|
#[error("Remote signing was rejected")]
|
||||||
Rejected,
|
Rejected,
|
||||||
|
|
||||||
|
#[error("Wallet address is not configured")]
|
||||||
|
WalletAddressNotConfigured,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClientTransport {
|
struct ClientTransport {
|
||||||
@@ -91,15 +174,27 @@ impl ClientTransport {
|
|||||||
|
|
||||||
pub struct ArbiterSigner {
|
pub struct ArbiterSigner {
|
||||||
transport: Mutex<ClientTransport>,
|
transport: Mutex<ClientTransport>,
|
||||||
address: Address,
|
address: Option<Address>,
|
||||||
chain_id: Option<ChainId>,
|
chain_id: Option<ChainId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArbiterSigner {
|
impl ArbiterSigner {
|
||||||
pub async fn connect_grpc(
|
pub async fn connect_grpc(url: ArbiterUrl) -> std::result::Result<Self, ConnectError> {
|
||||||
|
let storage = FileSigningKeyStorage::from_default_location()?;
|
||||||
|
Self::connect_grpc_with_storage(url, &storage).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_grpc_with_storage<S: SigningKeyStorage>(
|
||||||
|
url: ArbiterUrl,
|
||||||
|
storage: &S,
|
||||||
|
) -> std::result::Result<Self, ConnectError> {
|
||||||
|
let key = storage.load_or_create()?;
|
||||||
|
Self::connect_grpc_with_key(url, key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_grpc_with_key(
|
||||||
url: ArbiterUrl,
|
url: ArbiterUrl,
|
||||||
key: ed25519_dalek::SigningKey,
|
key: ed25519_dalek::SigningKey,
|
||||||
address: Address,
|
|
||||||
) -> std::result::Result<Self, ConnectError> {
|
) -> std::result::Result<Self, ConnectError> {
|
||||||
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
||||||
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
||||||
@@ -112,7 +207,7 @@ impl ArbiterSigner {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut client = ArbiterServiceClient::new(channel);
|
let mut client = ArbiterServiceClient::new(channel);
|
||||||
let (tx, rx) = mpsc::channel(16);
|
let (tx, rx) = mpsc::channel(BUFFER_LENGTH);
|
||||||
let response_stream = client.client(ReceiverStream::new(rx)).await?.into_inner();
|
let response_stream = client.client(ReceiverStream::new(rx)).await?.into_inner();
|
||||||
|
|
||||||
let mut transport = ClientTransport {
|
let mut transport = ClientTransport {
|
||||||
@@ -120,19 +215,37 @@ impl ArbiterSigner {
|
|||||||
receiver: response_stream,
|
receiver: response_stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
authenticate(&mut transport, key).await?;
|
authenticate(&mut transport, &key).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
transport: Mutex::new(transport),
|
transport: Mutex::new(transport),
|
||||||
address,
|
address: None,
|
||||||
chain_id: None,
|
chain_id: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign_transaction_via_arbiter(
|
pub fn wallet_address(&self) -> Option<Address> {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_wallet_address(&mut self, address: Option<Address>) {
|
||||||
|
self.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_wallet_address(mut self, address: Address) -> Self {
|
||||||
|
self.address = Some(address);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_chain_id(mut self, chain_id: ChainId) -> Self {
|
||||||
|
self.chain_id = Some(chain_id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_sign_transaction_request(
|
||||||
&self,
|
&self,
|
||||||
tx: &mut dyn SignableTransaction<Signature>,
|
tx: &mut dyn SignableTransaction<Signature>,
|
||||||
) -> Result<Signature> {
|
) -> Result<ClientRequest> {
|
||||||
if let Some(chain_id) = self.chain_id
|
if let Some(chain_id) = self.chain_id
|
||||||
&& !tx.set_chain_id_checked(chain_id)
|
&& !tx.set_chain_id_checked(chain_id)
|
||||||
{
|
{
|
||||||
@@ -145,15 +258,21 @@ impl ArbiterSigner {
|
|||||||
let mut rlp_transaction = Vec::new();
|
let mut rlp_transaction = Vec::new();
|
||||||
tx.encode_for_signing(&mut rlp_transaction);
|
tx.encode_for_signing(&mut rlp_transaction);
|
||||||
|
|
||||||
let request = ClientRequest {
|
let wallet_address = self
|
||||||
|
.address
|
||||||
|
.ok_or_else(|| Error::other(ClientSignError::WalletAddressNotConfigured))?;
|
||||||
|
|
||||||
|
Ok(ClientRequest {
|
||||||
payload: Some(ClientRequestPayload::EvmSignTransaction(
|
payload: Some(ClientRequestPayload::EvmSignTransaction(
|
||||||
EvmSignTransactionRequest {
|
EvmSignTransactionRequest {
|
||||||
wallet_address: self.address.as_slice().to_vec(),
|
wallet_address: wallet_address.as_slice().to_vec(),
|
||||||
rlp_transaction,
|
rlp_transaction,
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
};
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_sign_transaction_request(&self, request: ClientRequest) -> Result<Signature> {
|
||||||
let mut transport = self.transport.lock().await;
|
let mut transport = self.transport.lock().await;
|
||||||
transport.send(request).await.map_err(Error::other)?;
|
transport.send(request).await.map_err(Error::other)?;
|
||||||
let response = transport.recv().await.map_err(Error::other)?;
|
let response = transport.recv().await.map_err(Error::other)?;
|
||||||
@@ -181,9 +300,18 @@ impl ArbiterSigner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn authenticate(
|
fn map_connect_error(code: i32) -> ConnectError {
|
||||||
|
match client_connect_error::Code::try_from(code).unwrap_or(client_connect_error::Code::Unknown)
|
||||||
|
{
|
||||||
|
client_connect_error::Code::ApprovalDenied => ConnectError::ApprovalDenied,
|
||||||
|
client_connect_error::Code::NoUserAgentsOnline => ConnectError::NoUserAgentsOnline,
|
||||||
|
client_connect_error::Code::Unknown => ConnectError::UnexpectedAuthResponse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_auth_challenge_request(
|
||||||
transport: &mut ClientTransport,
|
transport: &mut ClientTransport,
|
||||||
key: ed25519_dalek::SigningKey,
|
key: &ed25519_dalek::SigningKey,
|
||||||
) -> std::result::Result<(), ConnectError> {
|
) -> std::result::Result<(), ConnectError> {
|
||||||
transport
|
transport
|
||||||
.send(ClientRequest {
|
.send(ClientRequest {
|
||||||
@@ -194,8 +322,12 @@ async fn authenticate(
|
|||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ConnectError::UnexpectedAuthResponse)?;
|
.map_err(|_| ConnectError::UnexpectedAuthResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_auth_challenge(
|
||||||
|
transport: &mut ClientTransport,
|
||||||
|
) -> std::result::Result<arbiter_proto::proto::client::AuthChallenge, ConnectError> {
|
||||||
let response = transport
|
let response = transport
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
@@ -203,7 +335,17 @@ async fn authenticate(
|
|||||||
|
|
||||||
let payload = response.payload.ok_or(ConnectError::MissingAuthChallenge)?;
|
let payload = response.payload.ok_or(ConnectError::MissingAuthChallenge)?;
|
||||||
match payload {
|
match payload {
|
||||||
ClientResponsePayload::AuthChallenge(challenge) => {
|
ClientResponsePayload::AuthChallenge(challenge) => Ok(challenge),
|
||||||
|
ClientResponsePayload::ClientConnectError(err) => Err(map_connect_error(err.code)),
|
||||||
|
_ => Err(ConnectError::UnexpectedAuthResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_auth_challenge_solution(
|
||||||
|
transport: &mut ClientTransport,
|
||||||
|
key: &ed25519_dalek::SigningKey,
|
||||||
|
challenge: arbiter_proto::proto::client::AuthChallenge,
|
||||||
|
) -> std::result::Result<(), ConnectError> {
|
||||||
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
|
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
|
||||||
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
|
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
|
||||||
|
|
||||||
@@ -214,28 +356,37 @@ async fn authenticate(
|
|||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
.map_err(|_| ConnectError::UnexpectedAuthResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive_auth_confirmation(
|
||||||
|
transport: &mut ClientTransport,
|
||||||
|
) -> std::result::Result<(), ConnectError> {
|
||||||
|
let response = transport
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
.map_err(|_| ConnectError::UnexpectedAuthResponse)?;
|
.map_err(|_| ConnectError::UnexpectedAuthResponse)?;
|
||||||
|
|
||||||
// Current server flow does not emit `AuthOk` for SDK clients, so we proceed after
|
let payload = response
|
||||||
// sending the solution. If authentication fails, the first business request will return
|
.payload
|
||||||
// a `ClientConnectError` or the stream will close.
|
.ok_or(ConnectError::UnexpectedAuthResponse)?;
|
||||||
Ok(())
|
match payload {
|
||||||
}
|
ClientResponsePayload::AuthOk(_) => Ok(()),
|
||||||
ClientResponsePayload::ClientConnectError(err) => {
|
ClientResponsePayload::ClientConnectError(err) => Err(map_connect_error(err.code)),
|
||||||
match client_connect_error::Code::try_from(err.code)
|
|
||||||
.unwrap_or(client_connect_error::Code::Unknown)
|
|
||||||
{
|
|
||||||
client_connect_error::Code::ApprovalDenied => Err(ConnectError::ApprovalDenied),
|
|
||||||
client_connect_error::Code::NoUserAgentsOnline => {
|
|
||||||
Err(ConnectError::NoUserAgentsOnline)
|
|
||||||
}
|
|
||||||
client_connect_error::Code::Unknown => Err(ConnectError::UnexpectedAuthResponse),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(ConnectError::UnexpectedAuthResponse),
|
_ => Err(ConnectError::UnexpectedAuthResponse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn authenticate(
|
||||||
|
transport: &mut ClientTransport,
|
||||||
|
key: &ed25519_dalek::SigningKey,
|
||||||
|
) -> std::result::Result<(), ConnectError> {
|
||||||
|
send_auth_challenge_request(transport, key).await?;
|
||||||
|
let challenge = receive_auth_challenge(transport).await?;
|
||||||
|
send_auth_challenge_solution(transport, key, challenge).await?;
|
||||||
|
receive_auth_confirmation(transport).await
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Signer for ArbiterSigner {
|
impl Signer for ArbiterSigner {
|
||||||
async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
|
async fn sign_hash(&self, _hash: &B256) -> Result<Signature> {
|
||||||
@@ -245,7 +396,7 @@ impl Signer for ArbiterSigner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn address(&self) -> Address {
|
fn address(&self) -> Address {
|
||||||
self.address
|
self.address.unwrap_or(Address::ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chain_id(&self) -> Option<ChainId> {
|
fn chain_id(&self) -> Option<ChainId> {
|
||||||
@@ -260,13 +411,70 @@ impl Signer for ArbiterSigner {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl TxSigner<Signature> for ArbiterSigner {
|
impl TxSigner<Signature> for ArbiterSigner {
|
||||||
fn address(&self) -> Address {
|
fn address(&self) -> Address {
|
||||||
self.address
|
self.address.unwrap_or(Address::ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign_transaction(
|
async fn sign_transaction(
|
||||||
&self,
|
&self,
|
||||||
tx: &mut dyn SignableTransaction<Signature>,
|
tx: &mut dyn SignableTransaction<Signature>,
|
||||||
) -> Result<Signature> {
|
) -> Result<Signature> {
|
||||||
self.sign_transaction_via_arbiter(tx).await
|
let request = self.build_sign_transaction_request(tx)?;
|
||||||
|
self.execute_sign_transaction_request(request).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
||||||
|
|
||||||
|
fn unique_temp_key_path() -> std::path::PathBuf {
|
||||||
|
let nanos = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.expect("clock should be after unix epoch")
|
||||||
|
.as_nanos();
|
||||||
|
std::env::temp_dir().join(format!(
|
||||||
|
"arbiter-client-key-{}-{}.bin",
|
||||||
|
std::process::id(),
|
||||||
|
nanos
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_storage_creates_and_reuses_key() {
|
||||||
|
let path = unique_temp_key_path();
|
||||||
|
let storage = FileSigningKeyStorage::new(path.clone());
|
||||||
|
|
||||||
|
let key_a = storage
|
||||||
|
.load_or_create()
|
||||||
|
.expect("first load_or_create should create key");
|
||||||
|
let key_b = storage
|
||||||
|
.load_or_create()
|
||||||
|
.expect("second load_or_create should read same key");
|
||||||
|
|
||||||
|
assert_eq!(key_a.to_bytes(), key_b.to_bytes());
|
||||||
|
assert!(path.exists());
|
||||||
|
|
||||||
|
std::fs::remove_file(path).expect("temp key file should be removable");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_storage_rejects_invalid_key_length() {
|
||||||
|
let path = unique_temp_key_path();
|
||||||
|
std::fs::write(&path, [42u8; 31]).expect("should write invalid key file");
|
||||||
|
let storage = FileSigningKeyStorage::new(path.clone());
|
||||||
|
|
||||||
|
let err = storage
|
||||||
|
.load_or_create()
|
||||||
|
.expect_err("storage should reject non-32-byte key file");
|
||||||
|
|
||||||
|
match err {
|
||||||
|
StorageError::InvalidKeyLength { expected, actual } => {
|
||||||
|
assert_eq!(expected, 32);
|
||||||
|
assert_eq!(actual, 31);
|
||||||
|
}
|
||||||
|
other => panic!("unexpected error: {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::remove_file(path).expect("temp key file should be removable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ rustls.workspace = true
|
|||||||
smlang.workspace = true
|
smlang.workspace = true
|
||||||
miette.workspace = true
|
miette.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
fatality = "0.1.1"
|
||||||
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
secrecy = "0.10.3"
|
secrecy = "0.10.3"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
format_challenge,
|
format_challenge,
|
||||||
proto::client::{
|
proto::client::{
|
||||||
AuthChallenge, AuthChallengeSolution, ClientConnectError, ClientRequest, ClientResponse,
|
AuthChallenge, AuthChallengeSolution, AuthOk, ClientConnectError, ClientRequest,
|
||||||
client_connect_error::Code as ConnectErrorCode,
|
ClientResponse, client_connect_error::Code as ConnectErrorCode,
|
||||||
client_request::Payload as ClientRequestPayload,
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
},
|
},
|
||||||
@@ -26,6 +26,25 @@ use crate::{
|
|||||||
|
|
||||||
use super::session::ClientSession;
|
use super::session::ClientSession;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ClientId(i32);
|
||||||
|
|
||||||
|
impl ClientId {
|
||||||
|
pub fn new(raw: i32) -> Self {
|
||||||
|
Self(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_i32(self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
struct ClientNonceState {
|
||||||
|
client_id: ClientId,
|
||||||
|
nonce: i32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Unexpected message payload")]
|
#[error("Unexpected message payload")]
|
||||||
@@ -63,7 +82,7 @@ pub enum ApproveError {
|
|||||||
async fn get_nonce(
|
async fn get_nonce(
|
||||||
db: &db::DatabasePool,
|
db: &db::DatabasePool,
|
||||||
pubkey: &VerifyingKey,
|
pubkey: &VerifyingKey,
|
||||||
) -> Result<Option<(i32, i32)>, Error> {
|
) -> Result<Option<ClientNonceState>, Error> {
|
||||||
let pubkey_bytes = pubkey.as_bytes().to_vec();
|
let pubkey_bytes = pubkey.as_bytes().to_vec();
|
||||||
|
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
@@ -90,7 +109,10 @@ async fn get_nonce(
|
|||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Some((client_id, current_nonce)))
|
Ok(Some(ClientNonceState {
|
||||||
|
client_id: ClientId::new(client_id),
|
||||||
|
nonce: current_nonce,
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -126,7 +148,7 @@ async fn approve_new_client(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum InsertClientResult {
|
enum InsertClientResult {
|
||||||
Inserted(i32),
|
Inserted(ClientId),
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +198,7 @@ async fn insert_client(
|
|||||||
Error::DatabaseOperationFailed
|
Error::DatabaseOperationFailed
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(InsertClientResult::Inserted(client_id))
|
Ok(InsertClientResult::Inserted(ClientId::new(client_id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn challenge_client(
|
async fn challenge_client(
|
||||||
@@ -224,6 +246,17 @@ async fn challenge_client(
|
|||||||
Error::InvalidChallengeSolution
|
Error::InvalidChallengeSolution
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
props
|
||||||
|
.transport
|
||||||
|
.send(Ok(ClientResponse {
|
||||||
|
payload: Some(ClientResponsePayload::AuthOk(AuthOk {})),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Failed to send auth ok");
|
||||||
|
Error::Transport
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +270,7 @@ fn connect_error_code(err: &Error) -> ConnectErrorCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn authenticate(props: &mut ClientConnection) -> Result<(VerifyingKey, i32), Error> {
|
async fn authenticate(props: &mut ClientConnection) -> Result<(VerifyingKey, ClientId), Error> {
|
||||||
let Some(ClientRequest {
|
let Some(ClientRequest {
|
||||||
payload: Some(ClientRequestPayload::AuthChallengeRequest(challenge)),
|
payload: Some(ClientRequestPayload::AuthChallengeRequest(challenge)),
|
||||||
}) = props.transport.recv().await
|
}) = props.transport.recv().await
|
||||||
@@ -253,13 +286,13 @@ async fn authenticate(props: &mut ClientConnection) -> Result<(VerifyingKey, i32
|
|||||||
VerifyingKey::from_bytes(pubkey_bytes).map_err(|_| Error::InvalidAuthPubkeyEncoding)?;
|
VerifyingKey::from_bytes(pubkey_bytes).map_err(|_| Error::InvalidAuthPubkeyEncoding)?;
|
||||||
|
|
||||||
let (client_id, nonce) = match get_nonce(&props.db, &pubkey).await? {
|
let (client_id, nonce) = match get_nonce(&props.db, &pubkey).await? {
|
||||||
Some((client_id, nonce)) => (client_id, nonce),
|
Some(state) => (state.client_id, state.nonce),
|
||||||
None => {
|
None => {
|
||||||
approve_new_client(&props.actors, pubkey).await?;
|
approve_new_client(&props.actors, pubkey).await?;
|
||||||
match insert_client(&props.db, &pubkey).await? {
|
match insert_client(&props.db, &pubkey).await? {
|
||||||
InsertClientResult::Inserted(client_id) => (client_id, 0),
|
InsertClientResult::Inserted(client_id) => (client_id, 0),
|
||||||
InsertClientResult::AlreadyExists => match get_nonce(&props.db, &pubkey).await? {
|
InsertClientResult::AlreadyExists => match get_nonce(&props.db, &pubkey).await? {
|
||||||
Some((client_id, nonce)) => (client_id, nonce),
|
Some(state) => (state.client_id, state.nonce),
|
||||||
None => return Err(Error::InternalError),
|
None => return Err(Error::InternalError),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use tracing::{error, info};
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
client::{ClientConnection, ClientError},
|
client::{ClientConnection, ClientError, auth::ClientId},
|
||||||
evm::ClientSignTransaction,
|
evm::ClientSignTransaction,
|
||||||
router::RegisterClient,
|
router::RegisterClient,
|
||||||
},
|
},
|
||||||
@@ -24,11 +24,11 @@ use crate::{
|
|||||||
|
|
||||||
pub struct ClientSession {
|
pub struct ClientSession {
|
||||||
props: ClientConnection,
|
props: ClientConnection,
|
||||||
client_id: i32,
|
client_id: ClientId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSession {
|
impl ClientSession {
|
||||||
pub(crate) fn new(props: ClientConnection, client_id: i32) -> Self {
|
pub(crate) fn new(props: ClientConnection, client_id: ClientId) -> Self {
|
||||||
Self { props, client_id }
|
Self { props, client_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ impl ClientSession {
|
|||||||
.actors
|
.actors
|
||||||
.evm
|
.evm
|
||||||
.ask(ClientSignTransaction {
|
.ask(ClientSignTransaction {
|
||||||
client_id: self.client_id,
|
client_id: self.client_id.as_i32(),
|
||||||
wallet_address: Address::from_slice(&wallet_address),
|
wallet_address: Address::from_slice(&wallet_address),
|
||||||
transaction: tx,
|
transaction: tx,
|
||||||
})
|
})
|
||||||
@@ -145,7 +145,7 @@ impl ClientSession {
|
|||||||
let props = ClientConnection::new(db, transport, actors);
|
let props = ClientConnection::new(db, transport, actors);
|
||||||
Self {
|
Self {
|
||||||
props,
|
props,
|
||||||
client_id: 0,
|
client_id: ClientId::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ async fn request_client_approval(
|
|||||||
while let Some(result) = pool.join_next().await {
|
while let Some(result) = pool.join_next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok(approved)) => {
|
Ok(Ok(approved)) => {
|
||||||
|
// cancel other pending requests
|
||||||
let _ = cancel_tx.send(());
|
let _ = cancel_tx.send(());
|
||||||
return Ok(approved);
|
return Ok(approved);
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,7 @@ impl MessageRouter {
|
|||||||
ctx: &mut Context<Self, DelegatedReply<Result<bool, ApprovalError>>>,
|
ctx: &mut Context<Self, DelegatedReply<Result<bool, ApprovalError>>>,
|
||||||
) -> DelegatedReply<Result<bool, ApprovalError>> {
|
) -> DelegatedReply<Result<bool, ApprovalError>> {
|
||||||
let (reply, Some(reply_sender)) = ctx.reply_sender() else {
|
let (reply, Some(reply_sender)) = ctx.reply_sender() else {
|
||||||
panic!("Exptected `request_client_approval` to have callback channel");
|
panic!("Expected `request_client_approval` to have callback channel");
|
||||||
};
|
};
|
||||||
|
|
||||||
let weak_refs = self
|
let weak_refs = self
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use arbiter_proto::{
|
|||||||
},
|
},
|
||||||
transport::Bi,
|
transport::Bi,
|
||||||
};
|
};
|
||||||
|
use fatality::Fatality;
|
||||||
use kameo::actor::Spawn as _;
|
use kameo::actor::Spawn as _;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
@@ -38,8 +39,8 @@ pub enum TransportResponseError {
|
|||||||
ConnectionRegistrationFailed,
|
ConnectionRegistrationFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransportResponseError {
|
impl Fatality for TransportResponseError {
|
||||||
pub fn is_terminal(&self) -> bool {
|
fn is_fatal(&self) -> bool {
|
||||||
!matches!(
|
!matches!(
|
||||||
self,
|
self,
|
||||||
Self::SdkClientApprove(_) | Self::SdkClientList(_) | Self::SdkClientRevoke(_)
|
Self::SdkClientApprove(_) | Self::SdkClientList(_) | Self::SdkClientRevoke(_)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use std::{ops::DerefMut, sync::Mutex};
|
|||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
evm as evm_proto,
|
evm as evm_proto,
|
||||||
user_agent::{
|
user_agent::{
|
||||||
ClientConnectionCancel, ClientConnectionRequest, SdkClientApproveRequest,
|
SdkClientApproveRequest, SdkClientApproveResponse, SdkClientConnectionCancel,
|
||||||
SdkClientApproveResponse, SdkClientEntry, SdkClientError as ProtoSdkClientError,
|
SdkClientConnectionRequest, SdkClientEntry, SdkClientError as ProtoSdkClientError,
|
||||||
SdkClientList, SdkClientListResponse, SdkClientRevokeRequest, SdkClientRevokeResponse,
|
SdkClientList, SdkClientListResponse, SdkClientRevokeRequest, SdkClientRevokeResponse,
|
||||||
UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest,
|
UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest,
|
||||||
UserAgentResponse, sdk_client_approve_response, sdk_client_list_response,
|
UserAgentResponse, sdk_client_approve_response, sdk_client_list_response,
|
||||||
@@ -16,6 +16,7 @@ use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
|||||||
use diesel::{ExpressionMethods as _, QueryDsl as _, dsl::insert_into};
|
use diesel::{ExpressionMethods as _, QueryDsl as _, dsl::insert_into};
|
||||||
use diesel_async::RunQueryDsl as _;
|
use diesel_async::RunQueryDsl as _;
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::VerifyingKey;
|
||||||
|
use fatality::Fatality;
|
||||||
use kameo::{Actor, error::SendError, messages, prelude::Context};
|
use kameo::{Actor, error::SendError, messages, prelude::Context};
|
||||||
use memsafe::MemSafe;
|
use memsafe::MemSafe;
|
||||||
use tokio::{select, sync::watch};
|
use tokio::{select, sync::watch};
|
||||||
@@ -127,7 +128,7 @@ impl UserAgentSession {
|
|||||||
ctx: &mut Context<Self, Result<bool, Error>>,
|
ctx: &mut Context<Self, Result<bool, Error>>,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
self.send_msg(
|
self.send_msg(
|
||||||
UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest {
|
UserAgentResponsePayload::SdkClientConnectionRequest(SdkClientConnectionRequest {
|
||||||
pubkey: client_pubkey.as_bytes().to_vec(),
|
pubkey: client_pubkey.as_bytes().to_vec(),
|
||||||
}),
|
}),
|
||||||
ctx,
|
ctx,
|
||||||
@@ -135,8 +136,9 @@ impl UserAgentSession {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let extractor = |msg| {
|
let extractor = |msg| {
|
||||||
if let UserAgentRequestPayload::ClientConnectionResponse(client_connection_response) =
|
if let UserAgentRequestPayload::SdkClientConnectionResponse(
|
||||||
msg
|
client_connection_response,
|
||||||
|
) = msg
|
||||||
{
|
{
|
||||||
Some(client_connection_response)
|
Some(client_connection_response)
|
||||||
} else {
|
} else {
|
||||||
@@ -148,7 +150,7 @@ impl UserAgentSession {
|
|||||||
_ = cancel_flag.changed() => {
|
_ = cancel_flag.changed() => {
|
||||||
info!(actor = "useragent", "client connection approval cancelled");
|
info!(actor = "useragent", "client connection approval cancelled");
|
||||||
self.send_msg(
|
self.send_msg(
|
||||||
UserAgentResponsePayload::ClientConnectionCancel(ClientConnectionCancel {}),
|
UserAgentResponsePayload::SdkClientConnectionCancel(SdkClientConnectionCancel {}),
|
||||||
ctx,
|
ctx,
|
||||||
).await?;
|
).await?;
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@@ -379,22 +381,15 @@ impl UserAgentSession {
|
|||||||
program_client::created_at.eq(now),
|
program_client::created_at.eq(now),
|
||||||
program_client::updated_at.eq(now),
|
program_client::updated_at.eq(now),
|
||||||
))
|
))
|
||||||
.execute(&mut conn)
|
.returning((
|
||||||
.await;
|
|
||||||
|
|
||||||
match insert_result {
|
|
||||||
Ok(_) => {
|
|
||||||
match program_client::table
|
|
||||||
.filter(program_client::public_key.eq(&pubkey_bytes))
|
|
||||||
.order(program_client::id.desc())
|
|
||||||
.select((
|
|
||||||
program_client::id,
|
program_client::id,
|
||||||
program_client::public_key,
|
program_client::public_key,
|
||||||
program_client::created_at,
|
program_client::created_at,
|
||||||
))
|
))
|
||||||
.first::<(i32, Vec<u8>, i32)>(&mut conn)
|
.get_result::<(i32, Vec<u8>, i32)>(&mut conn)
|
||||||
.await
|
.await;
|
||||||
{
|
|
||||||
|
match insert_result {
|
||||||
Ok((id, pubkey, created_at)) => Ok(response(
|
Ok((id, pubkey, created_at)) => Ok(response(
|
||||||
UserAgentResponsePayload::SdkClientApprove(SdkClientApproveResponse {
|
UserAgentResponsePayload::SdkClientApprove(SdkClientApproveResponse {
|
||||||
result: Some(ApproveResult::Client(SdkClientEntry {
|
result: Some(ApproveResult::Client(SdkClientEntry {
|
||||||
@@ -404,14 +399,6 @@ impl UserAgentSession {
|
|||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
Err(e) => {
|
|
||||||
error!(?e, "Failed to fetch inserted SDK client");
|
|
||||||
Err(TransportResponseError::SdkClientApprove(
|
|
||||||
ProtoSdkClientError::Internal,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(diesel::result::Error::DatabaseError(
|
Err(diesel::result::Error::DatabaseError(
|
||||||
diesel::result::DatabaseErrorKind::UniqueViolation,
|
diesel::result::DatabaseErrorKind::UniqueViolation,
|
||||||
_,
|
_,
|
||||||
@@ -573,7 +560,7 @@ impl Actor for UserAgentSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let should_stop = err.is_terminal();
|
let should_stop = err.is_fatal();
|
||||||
if self.props.transport.send(Err(err)).await.is_err() {
|
if self.props.transport.send(Err(err)).await.is_err() {
|
||||||
error!(actor = "useragent", reason = "channel closed", "send.failed");
|
error!(actor = "useragent", reason = "channel closed", "send.failed");
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
return Some(kameo::mailbox::Signal::Stop);
|
||||||
|
|||||||
@@ -114,6 +114,15 @@ pub async fn test_challenge_auth() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let response = test_transport.recv().await.expect("should receive auth ok");
|
||||||
|
match response {
|
||||||
|
Ok(resp) => match resp.payload {
|
||||||
|
Some(ClientResponsePayload::AuthOk(_)) => {}
|
||||||
|
other => panic!("Expected AuthOk, got {other:?}"),
|
||||||
|
},
|
||||||
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
|
}
|
||||||
|
|
||||||
// Auth completes, session spawned
|
// Auth completes, session spawned
|
||||||
task.await.unwrap();
|
task.await.unwrap();
|
||||||
}
|
}
|
||||||
@@ -178,6 +187,15 @@ pub async fn test_evm_sign_request_payload_is_handled() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let response = test_transport.recv().await.expect("should receive auth ok");
|
||||||
|
match response {
|
||||||
|
Ok(resp) => match resp.payload {
|
||||||
|
Some(ClientResponsePayload::AuthOk(_)) => {}
|
||||||
|
other => panic!("Expected AuthOk, got {other:?}"),
|
||||||
|
},
|
||||||
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
|
}
|
||||||
|
|
||||||
task.await.unwrap();
|
task.await.unwrap();
|
||||||
|
|
||||||
let tx = TxEip1559 {
|
let tx = TxEip1559 {
|
||||||
|
|||||||
Reference in New Issue
Block a user