Skip to content

Commit

Permalink
Remove Tor embedded library and use onion addresses (#662)
Browse files Browse the repository at this point in the history
The Tor option has been changed. Instead of starting an embedded Tor
library when the app starts, it requires Phoenix to use onion addresses
for the Peer and Electrum connections and delegates Tor management
to a third party app.

This is safer, as a failure in the Tor proxy cannot lead to a leak. It also 
improves payments reliability, since the Tor proxy app being a VPN app, 
its connection is much more reliable especially when Phoenix is in the
background. This app is also better maintained, and this frees Phoenix
from the complexity of managing a cross-platform Tor library.

Note that the custom Electrum server setting can opt-out from the onion
address requirement.

---------

Co-authored-by: Robbie Hanson <[email protected]>
  • Loading branch information
dpad85 and robbiehanson authored Feb 11, 2025
1 parent a1dd05d commit 6be7437
Show file tree
Hide file tree
Showing 82 changed files with 2,553 additions and 1,654 deletions.
1 change: 0 additions & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
object Versions {
const val lightningKmp = "1.8.5-SNAPSHOT"
const val secp256k1 = "0.14.0"
const val torMobile = "0.2.0"

const val kotlin = "1.9.22"

Expand Down
31 changes: 16 additions & 15 deletions phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.firebase.messaging.FirebaseMessaging
import fr.acinq.lightning.db.Bolt12IncomingPayment
import fr.acinq.lightning.db.ChannelCloseOutgoingPayment
import fr.acinq.lightning.db.IncomingPayment
import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.currentTimestampMillis
import fr.acinq.phoenix.PhoenixBusiness
Expand All @@ -85,7 +82,7 @@ import fr.acinq.phoenix.android.services.NodeServiceState
import fr.acinq.phoenix.android.settings.AboutView
import fr.acinq.phoenix.android.settings.AppAccessSettings
import fr.acinq.phoenix.android.settings.DisplayPrefsView
import fr.acinq.phoenix.android.settings.ElectrumView
import fr.acinq.phoenix.android.settings.electrum.ElectrumView
import fr.acinq.phoenix.android.settings.ExperimentalView
import fr.acinq.phoenix.android.settings.ForceCloseView
import fr.acinq.phoenix.android.settings.LogsView
Expand All @@ -112,7 +109,6 @@ import fr.acinq.phoenix.android.settings.walletinfo.SwapInWallet
import fr.acinq.phoenix.android.settings.walletinfo.WalletInfoView
import fr.acinq.phoenix.android.startup.LegacySwitcherView
import fr.acinq.phoenix.android.startup.StartupView
import fr.acinq.phoenix.android.utils.SystemNotificationHelper
import fr.acinq.phoenix.android.utils.appBackground
import fr.acinq.phoenix.android.utils.extensions.findActivitySafe
import fr.acinq.phoenix.android.utils.logger
Expand Down Expand Up @@ -367,10 +363,10 @@ fun AppView(
DisplaySeedView()
}
composable(Screen.ElectrumServer.route) {
ElectrumView()
ElectrumView(onBackClick = { navController.popBackStack() })
}
composable(Screen.TorConfig.route) {
TorConfigView()
TorConfigView(appViewModel = appVM, onBackClick = { navController.popBackStack() }, onBusinessTeardown = { navController.popToHome() })
}
composable(Screen.Channels.route) {
ChannelsView(
Expand Down Expand Up @@ -627,15 +623,20 @@ private fun RequireStarted(
nextUri: String? = null,
children: @Composable () -> Unit
) {
if (serviceState == null) {
// do nothing
} else if (serviceState !is NodeServiceState.Running) {
val nc = navController
nc.navigate("${Screen.Startup.route}?next=${nextUri?.encodeURLParameter()}") {
popUpTo(nc.graph.id) { inclusive = true }
children()

val navController = navController
val currentRoute = navController.currentDestination?.route
if (serviceState != null && serviceState is NodeServiceState.Off && currentRoute != null) {
if (currentRoute != Screen.Startup.route || currentRoute != Screen.SwitchToLegacy.route) {
val log = logger("Navigation")
LaunchedEffect(key1 = Unit) {
log.info("service off, navigating to startup then $nextUri")
navController.navigate("${Screen.Startup.route}?next=${nextUri?.encodeURLParameter()}") {
popUpTo(navController.graph.id) { inclusive = true }
}
}
}
} else {
children()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ fun Dialog(
title: String? = null,
properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
isScrollable: Boolean = true,
buttonsTopMargin: Dp = 24.dp,
buttons: (@Composable RowScope.() -> Unit)? = { Button(onClick = onDismiss, text = stringResource(id = R.string.btn_ok), padding = PaddingValues(16.dp)) },
buttonsTopMargin: Dp = 20.dp,
buttons: (@Composable RowScope.() -> Unit)? = { Button(onClick = onDismiss, text = stringResource(id = R.string.btn_ok), padding = PaddingValues(16.dp), shape = RoundedCornerShape(16.dp)) },
content: @Composable ColumnScope.() -> Unit,
) {
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss, properties = properties) {
Expand All @@ -79,10 +79,9 @@ fun Dialog(
// buttons
if (buttons != null) {
Spacer(Modifier.height(buttonsTopMargin))
Row(
modifier = Modifier
.align(Alignment.End)
) {
Row(modifier = Modifier
.align(Alignment.End)
.padding(8.dp)) {
buttons()
}
}
Expand Down Expand Up @@ -150,6 +149,18 @@ fun ConfirmDialog(
}
}

@Composable
fun NonDismissableDialog(
content: @Composable ColumnScope.() -> Unit
) {
androidx.compose.ui.window.Dialog(
onDismissRequest = {},
properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnBackPress = false, dismissOnClickOutside = false),
) {
DialogBody { content() }
}
}

@Composable
fun FullScreenDialog(
onDismiss: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ fun ProgressView(
progressCircleSize: Dp = 20.dp,
progressCircleWidth: Dp = 2.dp,
space: Dp = 8.dp,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
) {
Row(
modifier.padding(padding),
horizontalArrangement = horizontalArrangement,
) {
CircularProgressIndicator(Modifier.size(progressCircleSize), strokeWidth = progressCircleWidth)
Spacer(Modifier.width(space))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package fr.acinq.phoenix.android.home

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
Expand All @@ -30,24 +32,27 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.acinq.lightning.utils.Connection
import fr.acinq.phoenix.android.R
import fr.acinq.phoenix.android.components.Card
import fr.acinq.phoenix.android.components.Dialog
import fr.acinq.phoenix.android.components.HSeparator
import fr.acinq.phoenix.android.components.TextWithIcon
import fr.acinq.phoenix.android.userPrefs
import fr.acinq.phoenix.android.utils.extensions.isBadCertificate
import fr.acinq.phoenix.android.utils.monoTypo
import fr.acinq.phoenix.android.utils.mutedBgColor
import fr.acinq.phoenix.android.utils.negativeColor
import fr.acinq.phoenix.android.utils.orange
import fr.acinq.phoenix.android.utils.positiveColor
import fr.acinq.phoenix.managers.Connections
import fr.acinq.phoenix.utils.extensions.isOnion


@Composable
Expand All @@ -66,52 +71,46 @@ fun ConnectionDialog(
modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 24.dp)
)
} else {
if (connections.electrum != Connection.ESTABLISHED || connections.peer != Connection.ESTABLISHED) {
val isTorEnabled = userPrefs.getIsTorEnabled.collectAsState(initial = null).value
val hasConnectionIssues = connections.electrum != Connection.ESTABLISHED || connections.peer != Connection.ESTABLISHED
if (hasConnectionIssues) {
Text(text = stringResource(id = R.string.conndialog_summary_not_ok), Modifier.padding(horizontal = 24.dp))
}
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.height(16.dp))
HSeparator()

val isTorEnabled = userPrefs.getIsTorEnabled.collectAsState(initial = null).value
if (isTorEnabled != null && isTorEnabled) {
ConnectionDialogLine(label = stringResource(id = R.string.conndialog_tor), connection = connections.tor, onClick = onTorClick)
HSeparator()
}

ConnectionDialogLine(label = stringResource(id = R.string.conndialog_electrum), connection = connections.electrum, onClick = onElectrumClick) {
when (val connection = connections.electrum) {
Connection.ESTABLISHING -> {
Text(text = stringResource(R.string.conndialog_connecting), style = monoTypo)
}
Connection.ESTABLISHED -> {
Column {
Text(text = stringResource(R.string.conndialog_connected), style = monoTypo)
if (electrumBlockheight < 795_000) { // FIXME use a dynamic blockheight
TextWithIcon(
text = stringResource(id = R.string.conndialog_connected_electrum_behind, electrumBlockheight),
textStyle = MaterialTheme.typography.body1.copy(fontSize = 14.sp),
icon = R.drawable.ic_alert_triangle,
iconTint = negativeColor
)
}
}
Text(text = stringResource(R.string.conndialog_connected), style = monoTypo)
}
else -> {
Text(
text = if (connection is Connection.CLOSED && connection.isBadCertificate()) {
stringResource(R.string.conndialog_closed_bad_cert)
} else {
stringResource(R.string.conndialog_closed)
},
style = monoTypo
)
val customElectrumServer by userPrefs.getElectrumServer.collectAsState(initial = null)
if (isTorEnabled == true && customElectrumServer?.server?.isOnion == false && customElectrumServer?.requireOnionIfTorEnabled == true) {
TextWithIcon(text = stringResource(R.string.conndialog_electrum_not_onion), textStyle = monoTypo, icon = R.drawable.ic_alert_triangle, iconTint = negativeColor)
} else if (connection is Connection.CLOSED && connection.isBadCertificate()) {
TextWithIcon(text = stringResource(R.string.conndialog_closed_bad_cert), textStyle = monoTypo, icon = R.drawable.ic_alert_triangle, iconTint = negativeColor)
} else {
Text(text = stringResource(R.string.conndialog_closed), style = monoTypo)
}
}
}
}
HSeparator()
ConnectionDialogLine(label = stringResource(id = R.string.conndialog_lightning), connection = connections.peer)
HSeparator()
Spacer(Modifier.height(16.dp))


if (hasConnectionIssues && isTorEnabled == true) {
Card(backgroundColor = mutedBgColor, modifier = Modifier.fillMaxWidth(), internalPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp), onClick = onTorClick) {
TextWithIcon(text = stringResource(id = R.string.conndialog_tor_disclaimer_title), icon = R.drawable.ic_tor_shield, textStyle = MaterialTheme.typography.body2)
Spacer(modifier = Modifier.height(4.dp))
Text(text = stringResource(id = R.string.conndialog_tor_disclaimer_body))
}
}
}
}
}
Expand Down Expand Up @@ -147,7 +146,7 @@ private fun ConnectionDialogLine(
.then(
if (onClick != null) Modifier.clickable(role = Role.Button, onClickLabel = stringResource(id = R.string.conndialog_accessibility_desc, label), onClick = onClick) else Modifier
)
.padding(vertical = 12.dp, horizontal = 24.dp),
.padding(vertical = 16.dp, horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Surface(
Expand All @@ -161,6 +160,7 @@ private fun ConnectionDialogLine(
) {}
Spacer(modifier = Modifier.width(16.dp))
Text(text = label, modifier = Modifier.weight(1.0f))
Spacer(modifier = Modifier.width(24.dp))
content()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fun HomeBalance(
balanceDisplayMode: HomeAmountDisplayMode,
) {
if (balance == null) {
ProgressView(modifier = modifier, text = stringResource(id = R.string.home__balance_loading))
ProgressView(modifier = modifier, text = stringResource(id = R.string.home_balance_loading))
} else {
val isAmountRedacted = balanceDisplayMode == HomeAmountDisplayMode.REDACTED
Column(
Expand Down Expand Up @@ -134,7 +134,7 @@ private fun OnChainBalance(
) {
TextWithIcon(
text = if (balanceDisplayMode == HomeAmountDisplayMode.REDACTED) "****" else {
stringResource(id = R.string.home__onchain_incoming, availableOnchainBalance.toPrettyString(preferredAmountUnit, fiatRate, withUnit = true))
stringResource(id = R.string.home_onchain_incoming, availableOnchainBalance.toPrettyString(preferredAmountUnit, fiatRate, withUnit = true))
},
textStyle = MaterialTheme.typography.caption,
icon = R.drawable.ic_chain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fun ColumnScope.PaymentsList(
Column(modifier = modifier.weight(1f, fill = true), horizontalAlignment = Alignment.CenterHorizontally) {
if (payments.isEmpty()) {
Text(
text = stringResource(id = R.string.home__payments_none),
text = stringResource(id = R.string.home_payments_none),
style = MaterialTheme.typography.caption.copy(textAlign = TextAlign.Center, fontSize = 14.sp),
modifier = Modifier
.padding(horizontal = 32.dp)
Expand Down Expand Up @@ -80,7 +80,7 @@ private fun ColumnScope.LatestPaymentsList(
) {
val morePaymentsButton: @Composable () -> Unit = {
FilledButton(
text = stringResource(id = R.string.home__payments_more_button),
text = stringResource(id = R.string.home_payments_more_button),
icon = R.drawable.ic_chevron_down,
iconTint = MaterialTheme.typography.caption.color,
onClick = onPaymentsHistoryClick,
Expand Down
Loading

0 comments on commit 6be7437

Please sign in to comment.