Skip to content

Commit

Permalink
feat: tor now working with auth, auto boots after initially enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
Fonta1n3 committed Jul 13, 2024
1 parent 0bf8aec commit 4de796e
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 49 deletions.
8 changes: 8 additions & 0 deletions UnifyWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@
0A2C41BC2C3B4F1400D7EE9D /* KeyGen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyGen.swift; sourceTree = "<group>"; };
0A2C41BD2C3B4F1400D7EE9D /* shim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = shim.swift; sourceTree = "<group>"; };
0A2C41BE2C3B4F1400D7EE9D /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
0A5FAE072C41582300274B9B /* UnifyWalletmacOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "UnifyWalletmacOS-Info.plist"; sourceTree = "<group>"; };
0A5FAE082C415FBC00274B9B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0A8329C82C29F00F00F25C46 /* UnifyWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UnifyWallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
0A8329CB2C29F00F00F25C46 /* UnifyWalletApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifyWalletApp.swift; sourceTree = "<group>"; };
0A8329D02C29F00F00F25C46 /* UnifyWallet.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UnifyWallet.xcdatamodel; sourceTree = "<group>"; };
Expand Down Expand Up @@ -271,6 +273,7 @@
0A8329BF2C29F00F00F25C46 = {
isa = PBXGroup;
children = (
0A5FAE072C41582300274B9B /* UnifyWalletmacOS-Info.plist */,
0A8329CA2C29F00F00F25C46 /* UnifyWallet */,
0A8329C92C29F00F00F25C46 /* Products */,
C7EF38AFCE0F7DC024C5A76C /* Pods */,
Expand All @@ -290,6 +293,7 @@
0A8329CA2C29F00F00F25C46 /* UnifyWallet */ = {
isa = PBXGroup;
children = (
0A5FAE082C415FBC00274B9B /* Info.plist */,
0A2C41BF2C3B4F1400D7EE9D /* Base32 */,
0A832A022C29F21400F25C46 /* BitcoinCore */,
0A8329F72C29F21400F25C46 /* CodeScanner */,
Expand Down Expand Up @@ -927,6 +931,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = UnifyWallet/Info.plist;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down Expand Up @@ -972,6 +977,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = UnifyWallet/Info.plist;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down Expand Up @@ -1016,6 +1022,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "UnifyWalletmacOS-Info.plist";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down Expand Up @@ -1060,6 +1067,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
"FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "UnifyWalletmacOS-Info.plist";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
Expand Down
11 changes: 11 additions & 0 deletions UnifyWallet/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
72 changes: 44 additions & 28 deletions UnifyWallet/TorClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,34 +296,50 @@ class TorClient: NSObject, URLSessionDelegate {
DataManager.retrieve(entityName: "TorCredentials") { dict in
guard let dict = dict else { completion(); return }

let torCreds = TorCreds(dictionary: dict)

guard let encryptedPrivateKey = Crypto.decrypt(torCreds.encryptedPrivateKey) else { completion(); return }

guard let decryptedPrivateKey = Crypto.decrypt(encryptedPrivateKey) else { completion(); return }

guard let authKey = String(data: decryptedPrivateKey, encoding: .utf8) else { completion(); return }

guard let encryptedOnionAddress = torCreds.encryptedOnionAddress else { completion(); return }

guard let onionAddressData = Crypto.decrypt(encryptedOnionAddress) else { completion(); return }

guard let onionAddress = String(data: onionAddressData, encoding: .utf8) else { completion(); return }

let onionAddressArray = onionAddress.components(separatedBy: ".onion:")
// Ensure we are actually V3 before adding auth
guard onionAddressArray[0].count > 55 else { completion(); return }

let authString = onionAddressArray[0] + ":descriptor:x25519:" + authKey
let suffix = "UnifyWallet.auth_private"

let file = URL(fileURLWithPath: self.authDirPath, isDirectory: true).appendingPathComponent(suffix)

try? authString.write(to: file, atomically: true, encoding: .utf8)

try? (file as NSURL).setResourceValue(URLFileProtection.complete, forKey: .fileProtectionKey)

completion()
DataManager.retrieve(entityName: "RPCCredentials") { rpcCredsDict in
guard let rpcCredsDict = rpcCredsDict else {
completion()

return
}

guard let rpcAddress = rpcCredsDict["rpcAddress"] as? String else {
completion()

return
}

guard rpcAddress.hasSuffix(".onion") else {
completion()

return
}

let torCreds = TorCreds(dictionary: dict)

guard let decryptedPrivateKey = Crypto.decrypt(torCreds.encryptedPrivateKey) else {
completion()

return
}

guard let authKey = String(data: decryptedPrivateKey, encoding: .utf8) else {
completion()

return
}

let onionAddressArray = rpcAddress.components(separatedBy: ".onion")
// Ensure we are actually V3 before adding auth
guard onionAddressArray[0].count > 55 else { completion(); return }
let authString = onionAddressArray[0] + ":descriptor:x25519:" + authKey
let suffix = "UnifyWallet.auth_private"
let file = URL(fileURLWithPath: self.authDirPath, isDirectory: true).appendingPathComponent(suffix)
try? authString.write(to: file, atomically: true, encoding: .utf8)
try? (file as NSURL).setResourceValue(URLFileProtection.complete, forKey: .fileProtectionKey)

completion()
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion UnifyWallet/UnifyWalletApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ struct UnifyWalletApp: App {

let dict: [String:Any] = [
"rpcPass": encRpcPass,
"rpcUser": "Unify"
"rpcUser": "Unify",
"rpcAddress": "127.0.0.1",
"rpcPort": "38332"
]

saveCreds(entityName: "RPCCredentials", dict: dict)
Expand Down
41 changes: 29 additions & 12 deletions UnifyWallet/Views/Config/ConfigView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ struct ConfigView: View {
@State private var encSigner = ""
@State private var bitcoinCoreConnected = false
@State private var tint: Color = .red
@State private var chain = UserDefaults.standard.object(forKey: "network") as? String ?? "Signet"
@State private var chain = UserDefaults.standard.object(forKey: "network") as? String ?? "Regtest"
@State private var showingPassphraseAlert = false
@State private var passphrase = ""
@State private var passphraseConfirm = ""
@State private var creatingWallet = false
@State private var torEnabled = false
@State private var torEnabled = true
@State private var torProgress = 0.0
@State private var torConnected = false
@State private var torDifficulties = false
Expand Down Expand Up @@ -174,12 +174,9 @@ struct ConfigView: View {
.truncationMode(.middle)
.lineLimit(1)
.multilineTextAlignment(.leading)

Button {
print("update rpc address")
} label: {
Text("Save")
}
.onChange(of: rpcAddress) {
updateRpcAddress()
}
}

Text("Copy the auth text to add it to your bitcoin.conf. This will authorize Unify to communicate with your node.")
Expand All @@ -190,7 +187,7 @@ struct ConfigView: View {
}

Section("Tor") {
if torProgress < 100.0 && torEnabled {
if torEnabled && !torConnected {
ProgressView("Bootstrapping \(Int(torProgress))% complete…", value: torProgress, total: 100)
}

Expand Down Expand Up @@ -393,6 +390,9 @@ struct ConfigView: View {
rpcWallets.removeAll()
}
.onAppear {
if torManager.state == .connected {
torConnected = true
}
setValues()
}
.alert(errorDesc, isPresented: $showError) {
Expand Down Expand Up @@ -542,6 +542,7 @@ struct ConfigView: View {
}

rpcAddress = credentials["rpcAddress"] as? String ?? "127.0.0.1"
rpcPort = credentials["rpcPort"] as? String ?? "38332"

chain = UserDefaults.standard.object(forKey: "network") as? String ?? "Signet"

Expand All @@ -566,8 +567,7 @@ struct ConfigView: View {
if let walletName = UserDefaults.standard.object(forKey: "walletName") as? String {
rpcWallet = walletName
}

rpcPort = UserDefaults.standard.object(forKey: "rpcPort") as? String ?? "38332"

nostrRelay = UserDefaults.standard.object(forKey: "nostrRelay") as? String ?? "wss://relay.damus.io"

BitcoinCoreRPC.shared.btcRPC(method: .listwallets) { (response, errorDesc) in
Expand Down Expand Up @@ -649,7 +649,24 @@ struct ConfigView: View {


private func updateRpcPort() {
UserDefaults.standard.setValue(rpcPort, forKey: "rpcPort")
DataManager.update(keyToUpdate: "rpcPort", newValue: rpcPort, entity: "RPCCredentials") { rpcPortUpdated in
guard rpcPortUpdated else {
showError(desc: "Unable to update RPC port.")

return
}
}
}


private func updateRpcAddress() {
DataManager.update(keyToUpdate: "rpcAddress", newValue: rpcAddress, entity: "RPCCredentials") { rpcPortUpdated in
guard rpcPortUpdated else {
showError(desc: "Unable to update RPC address.")

return
}
}
}


Expand Down
50 changes: 44 additions & 6 deletions UnifyWallet/Views/Receive/ReceiveView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ struct ReceiveView: View {
@State private var utxos: [Utxo] = []
@State private var showError = false
@State private var errDesc = ""
@State private var torProgress = 0.0
@State private var torEnabled = false
@State private var showSpinner = false


var body: some View {
Form() {
if torProgress < 100.0 && torEnabled {
ProgressView("Tor bootstrapping \(Int(torProgress))% complete…", value: torProgress, total: 100)
}

Section("Create Invoice") {
HStack() {
Label("Invoice amount", systemImage: "bitcoinsign.circle")
Expand All @@ -43,6 +50,11 @@ struct ReceiveView: View {
.keyboardType(.default)
#endif
.autocorrectionDisabled()

if showSpinner {
ProgressView()
.scaleEffect(0.5)
}
}
}

Expand Down Expand Up @@ -80,14 +92,37 @@ struct ReceiveView: View {
amount = ""
address = ""
DataManager.retrieve(entityName: "RPCCredentials") { creds in
guard let _ = creds else {
guard let creds = creds else {
errDesc = "Looks like you are new here, go to Config to add the rpcauth to your bitcoin.conf and select a wallet."
showError = true
return
}

fetchAddress()
getUtxos()
guard let address = creds["rpcAddress"] as? String else { return }

if address.hasSuffix(".onion") {
torEnabled = UserDefaults.standard.object(forKey: "torEnabled") as? Bool ?? false
if torEnabled && TorClient.sharedInstance.state != .connected && TorClient.sharedInstance.state != .started {
TorClient.sharedInstance.start()
} else {
fetchAddress()
getUtxos()
}
} else {
fetchAddress()
getUtxos()
}
}

TorClient.sharedInstance.showProgress = { progress in
torProgress = Double(progress)
}

TorClient.sharedInstance.torConnected = { connected in
if connected {
fetchAddress()
getUtxos()
}
}
}
.alert(errDesc, isPresented: $showError) {
Expand All @@ -103,12 +138,13 @@ struct ReceiveView: View {


private func fetchAddress() {
showSpinner = true
let p = Get_New_Address(["address_type": "bech32"])

BitcoinCoreRPC.shared.btcRPC(method: .getnewaddress(param: p)) { (response, errorDesc) in
guard let address = response as? String else {
displayError(desc: errorDesc ?? "Unknown error from getnewaddress.")

showSpinner = false
return
}

Expand All @@ -124,13 +160,13 @@ struct ReceiveView: View {
BitcoinCoreRPC.shared.btcRPC(method: .listunspent(p)) { (response, errorDesc) in
guard let response = response as? [[String: Any]] else {
displayError(desc: errorDesc ?? "Unknown error from listunspent.")

showSpinner = false
return
}

guard response.count > 0 else {
displayError(desc: "No utxo's.")

showSpinner = false
return
}

Expand All @@ -143,6 +179,8 @@ struct ReceiveView: View {
}
}

showSpinner = false

if utxos.count == 0 {
displayError(desc: "No spendable utxo's.")
}
Expand Down
15 changes: 13 additions & 2 deletions UnifyWallet/Views/Send/SendView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ struct UploadInvoiceView: View {
@State private var isShowingScanner = false
@State private var pickerItem: PhotosPickerItem?
@State private var selectedImage: Image?
@State private var showError = false
@State private var errorDesc = ""

@Binding var uploadedInvoice: Invoice?
@Binding var invoiceUploaded: Bool
Expand Down Expand Up @@ -159,13 +161,22 @@ struct UploadInvoiceView: View {
#endif

Button {
uploadedInvoice = handlePaste()
invoiceUploaded = true
if let invoiceCheck = handlePaste() {
uploadedInvoice = invoiceCheck
invoiceUploaded = true
} else {
errorDesc = "Not a valid Payjoin over Nostr invoice."
showError = true
}

} label: {
Image(systemName: "doc.on.clipboard")
}
}
.buttonStyle(.bordered)
.alert(errorDesc, isPresented: $showError) {
Button("OK", role: .cancel) {}
}

Text("Select a method to upload an invoice.")
.foregroundStyle(.tertiary)
Expand Down
Loading

0 comments on commit 4de796e

Please sign in to comment.