Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ad5c745
tor: integrated client for listener, update tokio to last version
ardocrat May 22, 2026
0f88f7a
tor: send transaction with integrated client
ardocrat May 23, 2026
0195c16
build: code format
ardocrat May 23, 2026
8d5ac7b
tor: optimize bootstrap percent check
ardocrat Jun 7, 2026
379b094
tor: handle config build error
ardocrat Jun 7, 2026
efd73ce
tor: do not show slatepack address if tor not launched
ardocrat Jun 7, 2026
bef3c4c
tor: handle keystore errors
ardocrat Jun 7, 2026
aec5fa1
tor: keep service launched
ardocrat Jun 8, 2026
4316385
tor: bootstrap timeout, clean cache before client launch
ardocrat Jun 9, 2026
4ea8cd2
Merge branch 'staging' into arti
ardocrat Jun 9, 2026
51fe193
tor: post request timeout
ardocrat Jun 9, 2026
f35345f
fix: post timeout
ardocrat Jun 9, 2026
8d188df
Merge branch 'staging' into arti
ardocrat Jun 9, 2026
297e533
build: update deps after merge
ardocrat Jun 9, 2026
903fb38
tor: store arti runtime
ardocrat Jun 9, 2026
aeddb02
tor: do not send over tor if `manual` arg presents or `skip_send_atte…
ardocrat Jun 9, 2026
d9c72dd
fix: arg name
ardocrat Jun 9, 2026
7bb641b
tor: handle arti runtime creation error
ardocrat Jun 9, 2026
4871009
tor: handle connection error
ardocrat Jun 9, 2026
8982c91
tor: handle bootstrap error
ardocrat Jun 9, 2026
ca6c9ec
tor: handle start service name error
ardocrat Jun 9, 2026
e266085
tor: handle service config creation error
ardocrat Jun 9, 2026
77f3e22
tor: service proxy errors
ardocrat Jun 9, 2026
c2522b2
tor: handle service key creation emptiness
ardocrat Jun 9, 2026
f552f54
build: format code
ardocrat Jun 9, 2026
09fea00
build: update arti to 0.43, hyper to 0.10.1
ardocrat Jun 13, 2026
ee4dee2
api: start foreign listener before tor connection
ardocrat Jun 20, 2026
b614831
tor: handle json parse errors
ardocrat Jun 20, 2026
23341d3
tor: prevent to create several client runtimes, ability to recreate
ardocrat Jun 20, 2026
68f46de
tor: return bootstrap error
ardocrat Jun 21, 2026
192e14d
tor: handle host parse error
ardocrat Jun 21, 2026
ea8d3c6
tor: restart proxy on error
ardocrat Jun 21, 2026
a5dc72d
Merge remote-tracking branch 'grin-wallet/staging' into arti
ardocrat Jun 21, 2026
6182cf7
tor: do not use integrated client when config value use_integrated is…
ardocrat Jun 21, 2026
79d33b9
build: update lock file
ardocrat Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,900 changes: 6,563 additions & 2,337 deletions Cargo.lock

Large diffs are not rendered by default.

28 changes: 13 additions & 15 deletions api/src/owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use uuid::Uuid;
use crate::config::{TorConfig, WalletConfig};
use crate::core::core::OutputFeatures;
use crate::core::global;
use crate::impls::HttpSlateSender;
use crate::impls::SlateSender as _;
use crate::impls::TorSlateSender;
use crate::keychain::{Identifier, Keychain};
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
use crate::libwallet::api_impl::{owner, owner_updater};
Expand Down Expand Up @@ -2496,17 +2496,17 @@ pub fn try_slatepack_sync_workflow(
slate: &Slate,
dest: &str,
tor_config: Option<TorConfig>,
tor_sender: Option<HttpSlateSender>,
tor_sender: Option<TorSlateSender>,
send_to_finalize: bool,
test_mode: bool,
) -> Result<Option<Slate>, libwallet::Error> {
) -> Result<Option<Slate>, Error> {
if let Some(tc) = &tor_config {
if tc.skip_send_attempt == Some(true) {
return Ok(None);
}
}
let mut ret_slate = Slate::blank(2, false);
let mut send_sync = |mut sender: HttpSlateSender, method_str: &str| match sender
let mut send_sync = |mut sender: TorSlateSender, method_str: &str| match sender
.send_tx(&slate, send_to_finalize)
{
Ok(s) => {
Expand All @@ -2532,18 +2532,16 @@ pub fn try_slatepack_sync_workflow(
if test_mode {
None
} else {
match HttpSlateSender::with_socks_proxy(
&tor_addr.to_http_str(),
&tor_config.as_ref().unwrap().socks_proxy_addr,
&tor_config.as_ref().unwrap().send_config_dir,
tor_config.as_ref().unwrap().bridge.clone(),
tor_config.as_ref().unwrap().proxy.clone(),
) {
Ok(s) => Some(s),
Err(e) => {
debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e);
None
if let Some(tc) = tor_config {
match TorSlateSender::new(&tor_addr.to_http_str(), tc) {
Ok(s) => Some(s),
Err(e) => {
debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e);
None
}
}
} else {
return Err(Error::TorConfig("Tor config is not set".to_string()));
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions config/src/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,20 @@ fn comments() -> HashMap<String, String> {
"[tor]".to_string(),
"
#########################################
### TOR CONFIGURATION (Experimental) ###
### TOR CONFIGURATION ###
#########################################
"
.to_string(),
);

retval.insert(
"use_integrated".to_string(),
"
#Whether to use integrated Tor library
"
.to_string(),
);

retval.insert(
"skip_send_attempt".to_string(),
"
Expand Down Expand Up @@ -259,8 +267,11 @@ fn comments() -> HashMap<String, String> {
"
#Tor bridge relay: allow to send and receive via TOR in a country where it is censored.
#Enable it by entering a single bridge line. To disable it, you must comment it.
#Support of the transport: obfs4, meek and snowflake.
#obfs4proxy or snowflake client binary must be installed and on your path.
#Support of the transport: webtunnel, obfs4, and snowflake.
#webtunnel, obfs4proxy or snowflake client binary must be installed and on your path.
#Custom path for client binary
#bridge_bin_path = \"\"

#For example, the bridge line must be in the following format for obfs4 transport: \"obfs4 [IP:PORT] [FINGERPRINT] cert=[CERT] iat-mode=[IAT-MODE]\"
#bridge_line = \"\"

Expand Down
6 changes: 6 additions & 0 deletions config/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ impl From<io::Error> for ConfigError {
/// Tor configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TorConfig {
/// whether to use integrated Tor library
pub use_integrated: Option<bool>,
/// whether to skip any attempts to send via TOR
pub skip_send_attempt: Option<bool>,
/// Whether to start tor listener on listener startup (default true)
Expand All @@ -181,6 +183,7 @@ pub struct TorConfig {
impl Default for TorConfig {
fn default() -> TorConfig {
TorConfig {
use_integrated: Some(true),
skip_send_attempt: Some(false),
use_tor_listener: true,
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
Expand All @@ -194,6 +197,8 @@ impl Default for TorConfig {
/// Tor Bridge Config
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TorBridgeConfig {
/// Path to bridge binary to use with integrated Tor library
pub bridge_bin_path: Option<String>,
/// Bridge Line
pub bridge_line: Option<String>,
/// Client Option
Expand All @@ -203,6 +208,7 @@ pub struct TorBridgeConfig {
impl Default for TorBridgeConfig {
fn default() -> TorBridgeConfig {
TorBridgeConfig {
bridge_bin_path: None,
bridge_line: None,
client_option: None,
}
Expand Down
2 changes: 1 addition & 1 deletion controller/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ log = "0.4"
prettytable-rs = "0.10"
ring = "0.16"
term = "0.6"
tokio = { version = "0.2", features = ["full"] }
tokio = { version = "1.52.3", features = ["full"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
url = "2.1"
chrono = { version = "0.4.11", features = ["serde"] }
Expand Down
2 changes: 1 addition & 1 deletion controller/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ where
g_args.tls_conf.clone(),
tor_config.use_tor_listener,
test_mode,
Some(tor_config.clone()),
tor_config,
);
if let Err(e) = res {
error!("Error starting listener: {}", e);
Expand Down
121 changes: 63 additions & 58 deletions controller/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ use crate::apiwallet::{
};
use easy_jsonrpc_mw;
use easy_jsonrpc_mw::{Handler, MaybeReply};
use grin_wallet_impls::tor::arti::start_tor_service;
use grin_wallet_impls::tor::Tor;

lazy_static! {
pub static ref GRIN_OWNER_BASIC_REALM: HeaderValue =
Expand Down Expand Up @@ -82,32 +84,14 @@ fn check_middleware(
}

/// initiate the tor listener
fn init_tor_listener<L, C, K>(
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
fn init_tor_listener(
sec_key: SecretKey,
tor_dir: String,
addr: &str,
bridge: TorBridgeConfig,
tor_proxy: TorProxyConfig,
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
) -> Result<Tor, Error> {
let mut process = tor_process::TorProcess::new();
let mask = keychain_mask.lock();
// eventually want to read a list of service config keys
let mut w_lock = wallet.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&mask).as_ref())?;
let parent_key_id = w_inst.parent_key_id();
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let onion_address = OnionV3Address::from_private(&sec_key.0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;

let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
let mut tor_timeout = 20;
Expand All @@ -129,10 +113,6 @@ where
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
}

warn!(
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
onion_address, addr
);
tor_config::output_tor_listener_config(
&tor_dir,
addr,
Expand All @@ -149,7 +129,11 @@ where
.completion_percent(100)
.launch()
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
Ok((process, sp_address))
Ok(Tor {
process: Some(process),
service: None,
client: None,
})
}

/// Instantiate wallet Owner API for a single-use (command line) call
Expand Down Expand Up @@ -285,49 +269,67 @@ pub fn foreign_listener<L, C, K>(
tls_config: Option<TLSConfig>,
use_tor: bool,
test_mode: bool,
tor_config: Option<TorConfig>,
tor_config: TorConfig,
) -> Result<(), Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
C: NodeClient + 'static,
K: Keychain + 'static,
{
// Check if wallet has been opened first
{
let (sec_key, tor_dir, onion_address) = {
let mask = keychain_mask.lock();
let mut w_lock = wallet.lock();
let lc = w_lock.lc_provider()?;
let _ = lc.wallet_inst()?;
}

let (tor_bridge, tor_proxy) = match tor_config.clone() {
Some(s) => (s.bridge, s.proxy),
None => (TorBridgeConfig::default(), TorProxyConfig::default()),
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&mask).as_ref())?;
let parent_key_id = w_inst.parent_key_id();
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
let onion_address = OnionV3Address::from_private(&sec_key.0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
(sec_key, tor_dir, onion_address)
};

// need to keep in scope while the main listener is running
let (_tor_process, address) = match use_tor {
true => {
match init_tor_listener(
wallet.clone(),
keychain_mask.clone(),
// Need to keep external process in scope while the listener is running.
let tor_service = if use_tor {
let use_integrated = tor_config.use_integrated.unwrap_or(true);
let res = if use_integrated {
start_tor_service(sec_key, &tor_dir, addr, tor_config.clone())
Comment thread
ardocrat marked this conversation as resolved.
} else {
init_tor_listener(
sec_key,
tor_dir,
addr,
tor_bridge,
tor_proxy,
) {
Ok((tp, addr)) => (Some(tp), Some(addr)),
Err(e) => {
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
error!("Tor Error: {}", e);
warn!("Listener will be available via HTTP only");
(None, None)
}
tor_config.bridge.clone(),
tor_config.proxy.clone(),
)
};
match res {
Ok(service) => {
warn!(
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
onion_address, addr
);
Ok(Some(service))
}
Err(e) => {
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
error!("Tor Error: {}", e);
Err(e)
}
}
false => (None, None),
} else {
Ok(None)
};

let api_handler_v2 =
ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config));
let api_handler_v2 = ForeignAPIHandlerV2::new(
wallet,
keychain_mask,
test_mode,
Mutex::new(Some(tor_config)),
);
let mut router = Router::new();

router
Expand All @@ -343,14 +345,17 @@ where
let api_thread = apis
.start(socket_addr, router, tls_config, api_chan)
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;

warn!("HTTP Foreign listener started.");
if let Some(a) = address {
let qr_string = match QrCode::new(a.to_string()) {

if let Some(_) = tor_service.ok() {
Comment thread
ardocrat marked this conversation as resolved.
Outdated
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
let qr_string = match QrCode::new(sp_address.to_string()) {
Ok(qr) => qr.to_string(false, 3),
Err(_) => "Failed to generate QR code!".to_string(),
};
warn!("Slatepack Address is: {}\n{}", a, qr_string);
warn!("Slatepack Address is: {}\n{}", sp_address, qr_string);
} else {
warn!("Listener is available on {}", addr);
}

api_thread
Expand Down
23 changes: 18 additions & 5 deletions impls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,33 @@ ring = "0.16"
uuid = { version = "0.8", features = ["serde", "v4"] }
chrono = { version = "0.4.11", features = ["serde"] }
lazy_static = "1"
tokio = { version = "0.2", features = ["full"] }
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
tokio = { version = "1.52.3", features = ["full"] }
reqwest = { version = "0.13.3", features = ["socks"] }

#Socks/Tor/Bridge/Proxy
byteorder = "1"
ed25519-dalek = "1.0.0-pre.4"
x25519-dalek = "0.6"
data-encoding = "2"
arti-ed25519-dalek = { version = "2.1.1", package = "ed25519-dalek" }
regex = "1.3"
timer = "0.2"
sysinfo = "0.29"
base64 = "0.12.0"
url = "2.1"

arti-client = { version = "0.42.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] }
tor-rtcompat = { version = "0.42.0", features = ["static"] }
fs-mistrust = "0.14.1"
tor-hsservice = "0.42.0"
tor-hsrproxy = "0.42.0"
tor-keymgr = "0.42.0"
tor-llcrypto = "0.42.0"
tor-hscrypto = "0.42.0"
sha2 = "0.10.8"
curve25519-dalek = "4.1.3"
hyper = { version = "1.9.0", features = ["http1", "client"] }
hyper-util = { version = "0.1.20", features = ["tokio"] }
http-body-util = "0.1.3"
bytes = "1.11.1"

grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" }
Expand Down
4 changes: 2 additions & 2 deletions impls/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
// limitations under the License.

mod file;
pub mod http;
mod slatepack;
pub mod tor;

pub use self::file::PathToSlate;
pub use self::http::HttpSlateSender;
pub use self::slatepack::PathToSlatepack;
pub use self::tor::TorSlateSender;

use crate::config::WalletConfig;
use crate::libwallet::{Error, Slate};
Expand Down
Loading
Loading