Skip to content

Commit

Permalink
Add enabled wallets to WalletBackup
Browse files Browse the repository at this point in the history
  • Loading branch information
ant013 committed Sep 14, 2023
1 parent 804203e commit 3dcb8d0
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 46 deletions.
55 changes: 53 additions & 2 deletions UnstoppableWallet/UnstoppableWallet/Core/Crypto/WalletBackup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ class WalletBackup: Codable {
let isManualBackedUp: Bool
let version: Int
let timestamp: TimeInterval?
let enabledWallets: [EnabledWallet]

enum CodingKeys: String, CodingKey {
case crypto
case enabledWallets = "enabled_wallets"
case id
case type
case isManualBackedUp = "manual_backup"
case version
case timestamp
}

init(crypto: WalletBackupCrypto, id: String, type: AccountType.Abstract, isManualBackedUp: Bool, version: Int, timestamp: TimeInterval) {
init(crypto: WalletBackupCrypto, enabledWallets: [EnabledWallet], id: String, type: AccountType.Abstract, isManualBackedUp: Bool, version: Int, timestamp: TimeInterval) {
self.crypto = crypto
self.enabledWallets = enabledWallets
self.id = id
self.type = type
self.isManualBackedUp = isManualBackedUp
Expand All @@ -29,6 +32,7 @@ class WalletBackup: Codable {
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
crypto = try container.decode(WalletBackupCrypto.self, forKey: .crypto)
enabledWallets = (try? container.decode([EnabledWallet].self, forKey: .enabledWallets)) ?? []
id = try container.decode(String.self, forKey: .id)
type = try container.decode(AccountType.Abstract.self, forKey: .type)
let isManualBackedUp = try? container.decode(Bool.self, forKey: .isManualBackedUp)
Expand All @@ -40,12 +44,59 @@ class WalletBackup: Codable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(crypto, forKey: .crypto)
try container.encode(enabledWallets, forKey: .enabledWallets)
try container.encode(id, forKey: .id)
try container.encode(type, forKey: .type)
try container.encode(isManualBackedUp, forKey: .isManualBackedUp)
try container.encode(version, forKey: .version)
try container.encode(timestamp, forKey: .timestamp)
}

}

extension WalletBackup {
struct EnabledWallet: Codable {
let tokenQueryId: String
let coinName: String?
let coinCode: String?
let tokenDecimals: Int?

enum CodingKeys: String, CodingKey {
case tokenQueryId = "token_query_id"
case coinName = "coin_name"
case coinCode = "coin_code"
case tokenDecimals = "decimals"
}

init(tokenQueryId: String, coinName: String?, coinCode: String?, tokenDecimals: Int?) {
self.tokenQueryId = tokenQueryId
self.coinName = coinName
self.coinCode = coinCode
self.tokenDecimals = tokenDecimals
}

init(_ wallet: Wallet) {
tokenQueryId = wallet.token.tokenQuery.id
coinName = wallet.coin.name
coinCode = wallet.coin.code
tokenDecimals = wallet.decimals
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tokenQueryId = try container.decode(String.self, forKey: .tokenQueryId)
let coinName = try? container.decode(String.self, forKey: .coinName)
let coinCode = try container.decode(String.self, forKey: .coinCode)
let tokenDecimals = try container.decode(Int.self, forKey: .tokenDecimals)

self.init(tokenQueryId: tokenQueryId, coinName: coinName, coinCode: coinCode, tokenDecimals: tokenDecimals)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tokenQueryId, forKey: .tokenQueryId)
try container.encode(coinName, forKey: .coinName)
try container.encode(coinCode, forKey: .coinCode)
try container.encode(tokenDecimals, forKey: .tokenDecimals)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@ extension CloudAccountBackupManager {
iCloudUrl != nil
}

func save(accountType: AccountType, isManualBackedUp: Bool, passphrase: String, name: String) throws {
func save(accountType: AccountType, wallets: [Wallet], isManualBackedUp: Bool, passphrase: String, name: String) throws {
guard let iCloudUrl else {
throw BackupError.urlNotAvailable
}

do {
let name = name + Self.fileExtension
let encoded = try WalletBackupConverter.encode(accountType: accountType, isManualBackedUp: isManualBackedUp, passphrase: passphrase)
let encoded = try WalletBackupConverter.encode(accountType: accountType, wallets: wallets.map { WalletBackup.EnabledWallet($0) }, isManualBackedUp: isManualBackedUp, passphrase: passphrase)

try fileStorage.write(directoryUrl: iCloudUrl, filename: name, data: encoded)
logger?.log(level: .debug, message: "CloudAccountManager.downloadItems, save \(name)")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import StorageKit
import MarketKit
import StorageKit

class LocalStorage {
private let agreementAcceptedKey = "i_understand_key"
Expand All @@ -25,11 +25,9 @@ class LocalStorage {
init(storage: StorageKit.ILocalStorage) {
self.storage = storage
}

}

extension LocalStorage {

var debugLog: String? {
get { storage.value(for: debugLogKey) }
set { storage.set(value: newValue, for: debugLogKey) }
Expand Down Expand Up @@ -100,5 +98,64 @@ extension LocalStorage {
get { storage.value(for: keyTelegramSupportRequested) ?? false }
set { storage.set(value: newValue, for: keyTelegramSupportRequested) }
}
}

extension LocalStorage {
var backup: [String: Any] {
var fields: [String: Any] = [
"lock_time_enabled": lockTimeEnabled.description,
"contacts_sync": remoteContactsSync.description,
"show_indicators": indicatorsShown.description,
]

if let chartIndicators {
fields["chart_indicators"] = chartIndicators.hs.hexString
}

let providers: [String: String] = BlockchainType
.supported
.filter { !$0.allowedProviders.isEmpty }
.map { ($0.uid, defaultProvider(blockchainType: $0).rawValue) }
.reduce(into: [:]) { $0[$1.0] = $1.1 }

fields["swap_providers"] = providers

return fields
}

private func bool(value: Any?) -> Bool? {
if let string = value as? String,
let enabled = Bool(string) {
return enabled
}
return nil
}

func restore(backup _: [String: Any]) {
if let lockTime = bool(value: backup["lock_time_enabled"]) {
lockTimeEnabled = lockTime
}
if let contactsSync = bool(value: backup["contacts_sync"]) {
remoteContactsSync = contactsSync
}
if let showIndicators = bool(value: backup["show_indicators"]) {
indicatorsShown = showIndicators
}

if let chartIndicators = backup["chart_indicators"] as? String,
let data = chartIndicators.hs.hexData {
self.chartIndicators = data
}

if let providers = backup["swap_providers"] as? [String: String] {
providers.forEach { key, value in
let type = BlockchainType(uid: key)
if let provider = SwapModule.Dex.Provider(rawValue: value),
!type.allowedProviders.isEmpty {

setDefaultProvider(blockchainType: type, provider: provider)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class BackupCloudModule {
}

static func backupPassword(account: Account, name: String) -> UIViewController {
let service = BackupCloudPassphraseService(iCloudManager: App.shared.cloudAccountBackupManager, account: account, name: name)
let service = BackupCloudPassphraseService(iCloudManager: App.shared.cloudAccountBackupManager, walletManager: App.shared.walletManager, account: account, name: name)
let viewModel = BackupCloudPassphraseViewModel(service: service)
let controller = BackupCloudPassphraseViewController(viewModel: viewModel)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import Foundation

class BackupCloudPassphraseService {
private let iCloudManager: CloudAccountBackupManager
private let walletManager: WalletManager
private let account: Account
private let name: String

var passphrase: String = ""
var passphraseConfirmation: String = ""

init(iCloudManager: CloudAccountBackupManager, account: Account, name: String) {
init(iCloudManager: CloudAccountBackupManager, walletManager: WalletManager, account: Account, name: String) {
self.iCloudManager = iCloudManager
self.walletManager = walletManager
self.account = account
self.name = name
}
Expand Down Expand Up @@ -41,7 +43,8 @@ extension BackupCloudPassphraseService {
}

do {
try iCloudManager.save(accountType: account.type, isManualBackedUp: account.backedUp, passphrase: passphrase, name: name)
let wallets = App.shared.walletManager.wallets(account: account)
try iCloudManager.save(accountType: account.type, wallets: wallets, isManualBackedUp: account.backedUp, passphrase: passphrase, name: name)
} catch {
if case .urlNotAvailable = error as? CloudAccountBackupManager.BackupError {
throw CreateError.urlNotAvailable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
import Foundation
import MarketKit

class WalletBackupConverter {
private static let version = 1
private static let version = 2

static func encode(accountType: AccountType, isManualBackedUp: Bool, passphrase: String) throws -> Data {
static func encode(accountType: AccountType, wallets: [WalletBackup.EnabledWallet], isManualBackedUp: Bool, passphrase: String) throws -> Data {
let message = accountType.uniqueId(hashed: false)
let iv = BackupCryptoHelper.generateInitialVector().hs.hex

let cipherText = try BackupCryptoHelper.AES128(
operation: .encrypt,
ivHex: iv,
pass: passphrase,
message: message,
kdf: .defaultBackup
operation: .encrypt,
ivHex: iv,
pass: passphrase,
message: message,
kdf: .defaultBackup
)
let encodedCipherText = cipherText.base64EncodedString()
let mac = try BackupCryptoHelper.mac(
pass: passphrase,
message: encodedCipherText.hs.data,
kdf: .defaultBackup
pass: passphrase,
message: encodedCipherText.hs.data,
kdf: .defaultBackup
)

let crypto = WalletBackupCrypto(
cipher: BackupCryptoHelper.defaultCypher,
cipherParams: CipherParams(iv: iv),
cipherText: encodedCipherText,
kdf: BackupCryptoHelper.defaultKdf,
kdfParams: .defaultBackup,
mac: mac.hs.hex)
cipher: BackupCryptoHelper.defaultCypher,
cipherParams: CipherParams(iv: iv),
cipherText: encodedCipherText,
kdf: BackupCryptoHelper.defaultKdf,
kdfParams: .defaultBackup,
mac: mac.hs.hex
)
let backup = WalletBackup(
crypto: crypto,
id: accountType.uniqueId().hs.hex,
type: AccountType.Abstract(accountType),
isManualBackedUp: isManualBackedUp,
version: Self.version,
timestamp: Date().timeIntervalSince1970
crypto: crypto,
enabledWallets: wallets,
id: accountType.uniqueId().hs.hex,
type: AccountType.Abstract(accountType),
isManualBackedUp: isManualBackedUp,
version: Self.version,
timestamp: Date().timeIntervalSince1970
)
return try JSONEncoder().encode(backup)

}

static func decode(data: Data, passphrase: String) throws -> AccountType {
Expand All @@ -48,26 +50,24 @@ class WalletBackupConverter {
}

let decryptData = try BackupCryptoHelper.AES128(
operation: .decrypt,
ivHex: backup.crypto.cipherParams.iv,
pass: passphrase,
message: message,
kdf: .defaultBackup)
operation: .decrypt,
ivHex: backup.crypto.cipherParams.iv,
pass: passphrase,
message: message,
kdf: .defaultBackup
)

guard let accountType = AccountType.decode(uniqueId: decryptData, type: backup.type) else {
throw CodingError.cantDecodeAccountType
}

return accountType
}

}

extension WalletBackupConverter {

enum CodingError: Error {
case cantDecodeCipherText
case cantDecodeAccountType
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct RestoreCloudModule {
let name: String
let accountType: AccountType
let isManualBackedUp: Bool
let showSelectCoins: Bool
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class RestoreCloudPassphraseModule {
iCloudManager: App.shared.cloudAccountBackupManager,
accountFactory: App.shared.accountFactory,
accountManager: App.shared.accountManager,
walletManager: App.shared.walletManager,
item: item
)
let viewModel = RestoreCloudPassphraseViewModel(service: service)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@ class RestoreCloudPassphraseService {
private let iCloudManager: CloudAccountBackupManager
private let accountFactory: AccountFactory
private let accountManager: AccountManager
private let walletManager: WalletManager

private let restoredBackup: RestoreCloudModule.RestoredBackup

var passphrase: String = ""

init(iCloudManager: CloudAccountBackupManager, accountFactory: AccountFactory, accountManager: AccountManager, item: RestoreCloudModule.RestoredBackup) {
init(iCloudManager: CloudAccountBackupManager, accountFactory: AccountFactory, accountManager: AccountManager, walletManager: WalletManager, item: RestoreCloudModule.RestoredBackup) {
self.iCloudManager = iCloudManager
self.accountFactory = accountFactory
self.accountManager = accountManager
self.restoredBackup = item
self.walletManager = walletManager
restoredBackup = item
}

private func createAccount(accountType: AccountType) {
let account = accountFactory.account(type: accountType, origin: .restored, backedUp: restoredBackup.walletBackup.isManualBackedUp, name: restoredBackup.name)
accountManager.save(account: account)

let wallets = restoredBackup.walletBackup.enabledWallets.map {
EnabledWallet(
tokenQueryId: $0.tokenQueryId,
accountId: account.id,
coinName: $0.coinName,
coinCode: $0.coinCode,
tokenDecimals: $0.tokenDecimals
)
}
walletManager.save(enabledWallets: wallets)
}

}
Expand Down Expand Up @@ -77,10 +95,12 @@ extension RestoreCloudPassphraseService {
accountManager.save(account: account)
return .success
default:
createAccount(accountType: accountType)
return .restoredAccount(RestoreCloudModule.RestoredAccount(
name: restoredBackup.name,
accountType: accountType,
isManualBackedUp: restoredBackup.walletBackup.isManualBackedUp
isManualBackedUp: restoredBackup.walletBackup.isManualBackedUp,
showSelectCoins: restoredBackup.walletBackup.enabledWallets.isEmpty
))
}
} catch {
Expand Down
Loading

0 comments on commit 3dcb8d0

Please sign in to comment.