Skip to content

Commit

Permalink
Implement classes for full backup
Browse files Browse the repository at this point in the history
  • Loading branch information
ant013 committed Sep 21, 2023
1 parent 1515336 commit 2cda645
Show file tree
Hide file tree
Showing 49 changed files with 985 additions and 535 deletions.
54 changes: 30 additions & 24 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

30 changes: 27 additions & 3 deletions UnstoppableWallet/UnstoppableWallet/Core/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MarketKit
import PinKit
import StorageKit
import ThemeKit
import LanguageKit

class App {
static var instance: App?
Expand Down Expand Up @@ -105,7 +106,8 @@ class App {

let appManager: AppManager
let contactManager: ContactBookManager
let cloudAccountBackupManager: CloudAccountBackupManager
let appBackupProvider: AppBackupProvider
let cloudBackupManager: CloudBackupManager

let appEventHandler = EventHandler()

Expand Down Expand Up @@ -191,8 +193,6 @@ class App {
restoreSettingsManager = RestoreSettingsManager(storage: restoreSettingsStorage)
predefinedBlockchainService = PredefinedBlockchainService(restoreSettingsManager: restoreSettingsManager)

cloudAccountBackupManager = CloudAccountBackupManager(ubiquityContainerIdentifier: AppConfig.sharedCloudContainer, restoreSettingsManager: restoreSettingsManager, logger: logger)

let hsLabelProvider = HsLabelProvider(networkManager: networkManager)
let evmLabelStorage = EvmLabelStorage(dbPool: dbPool)
let syncerStateStorage = SyncerStateStorage(dbPool: dbPool)
Expand Down Expand Up @@ -306,6 +306,30 @@ class App {
let cexAssetRecordStorage = CexAssetRecordStorage(dbPool: dbPool)
cexAssetManager = CexAssetManager(accountManager: accountManager, marketKit: marketKit, storage: cexAssetRecordStorage)

appBackupProvider = AppBackupProvider(
accountManager: accountManager,
accountFactory: accountFactory,
walletManager: walletManager,
favoritesManager: favoritesManager,
evmSyncSourceManager: evmSyncSourceManager,
restoreSettingsManager: restoreSettingsManager,
localStorage: localStorage,
languageManager: LanguageManager.shared,
currencyKit: currencyKit,
themeManager: themeManager,
launchScreenManager: launchScreenManager,
appIconManager: appIconManager,
balancePrimaryValueManager: balancePrimaryValueManager,
balanceConversionManager: balanceConversionManager,
balanceHiddenManager: balanceHiddenManager,
contactManager: contactManager
)
cloudBackupManager = CloudBackupManager(
ubiquityContainerIdentifier: AppConfig.sharedCloudContainer,
appBackupProvider: appBackupProvider,
logger: logger
)

appManager = AppManager(
accountManager: accountManager,
walletManager: walletManager,
Expand Down

This file was deleted.

141 changes: 141 additions & 0 deletions UnstoppableWallet/UnstoppableWallet/Core/Crypto/BackupCrypto.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import Foundation

class BackupCrypto: Codable {
static var defaultBackup = KdfParams(dklen: 32, n: 16384, p: 4, r: 8, salt: AppConfig.backupSalt)

let cipher: String
let cipherParams: CipherParams
let cipherText: String
let kdf: String
let kdfParams: KdfParams
let mac: String

enum CodingKeys: String, CodingKey {
case cipher
case cipherParams = "cipherparams"
case cipherText = "ciphertext"
case kdf
case kdfParams = "kdfparams"
case mac
}

init(cipher: String, cipherParams: CipherParams, cipherText: String, kdf: String, kdfParams: KdfParams, mac: String) {
self.cipher = cipher
self.cipherParams = cipherParams
self.cipherText = cipherText
self.kdf = kdf
self.kdfParams = kdfParams
self.mac = mac
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
cipher = try container.decode(String.self, forKey: .cipher)
cipherParams = try container.decode(CipherParams.self, forKey: .cipherParams)
cipherText = try container.decode(String.self, forKey: .cipherText)
kdf = try container.decode(String.self, forKey: .kdf)
kdfParams = try container.decode(KdfParams.self, forKey: .kdfParams)
mac = try container.decode(String.self, forKey: .mac)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(cipher, forKey: .cipher)
try container.encode(cipherParams, forKey: .cipherParams)
try container.encode(cipherText, forKey: .cipherText)
try container.encode(kdf, forKey: .kdf)
try container.encode(kdfParams, forKey: .kdfParams)
try container.encode(mac, forKey: .mac)
}
}

extension BackupCrypto {
func data(passphrase: String) throws -> Data {
try Self.validate(passphrase: passphrase)
// Validation data
guard let data = Data(base64Encoded: cipherText) else {
throw RestoreCloudModule.RestoreError.invalidBackup
}

// validation passphrase
let isValid = (try? BackupCryptoHelper.isValid(
macHex: mac,
pass: passphrase,
message: cipherText.hs.data,
kdf: kdfParams
)) ?? false
guard isValid else {
throw RestoreCloudModule.RestoreError.invalidPassword
}

return try BackupCryptoHelper.AES128(
operation: .decrypt,
ivHex: cipherParams.iv,
pass: passphrase,
message: data,
kdf: kdfParams
)
}

func accountType(type: AccountType.Abstract, passphrase: String) throws -> AccountType {
let data = try data(passphrase: passphrase)

guard let accountType = AccountType.decode(uniqueId: data, type: type) else {
throw RestoreCloudModule.RestoreError.invalidBackup
}

return accountType
}
}

extension BackupCrypto {
static func validate(passphrase: String) throws {
// Validation passphrase
guard !passphrase.isEmpty else {
throw ValidationError.emptyPassphrase
}
guard passphrase.count >= BackupCloudModule.minimumPassphraseLength else {
throw ValidationError.simplePassword
}

let allSatisfy = BackupCloudModule.PassphraseCharacterSet.allCases.allSatisfy { set in set.contains(passphrase) }
if !allSatisfy {
throw ValidationError.simplePassword
}
}

static func instance(data: Data, passphrase: String, kdf: KdfParams = .defaultBackup) throws -> BackupCrypto {
let iv = BackupCryptoHelper.generateInitialVector().hs.hex

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

let encodedCipherText = cipherText.base64EncodedString()
let mac = try BackupCryptoHelper.mac(
pass: passphrase,
message: encodedCipherText.hs.data,
kdf: kdf
)

return BackupCrypto(
cipher: BackupCryptoHelper.defaultCypher,
cipherParams: CipherParams(iv: iv),
cipherText: encodedCipherText,
kdf: BackupCryptoHelper.defaultKdf,
kdfParams: kdf,
mac: mac.hs.hex
)
}
}

extension BackupCrypto {
enum ValidationError: Error {
case emptyPassphrase
case simplePassword
}
}
42 changes: 39 additions & 3 deletions UnstoppableWallet/UnstoppableWallet/Core/Crypto/FullBackup.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
import Foundation

struct FullBackup {
let wallets: [WalletBackup]
let id: String
let wallets: [RestoreCloudModule.RestoredBackup]
let watchlistIds: [String]
let contacts: ContactBook?
let appearance: AppearanceBackup?
let contacts: BackupCrypto?
let settings: SettingsBackup?
let version: Int
let timestamp: TimeInterval?
}

extension FullBackup: Codable {
enum CodingKeys: String, CodingKey {
case id
case wallets
case watchlistIds = "watchlist"
case contacts
case settings
case version
case timestamp
}

// init(from decoder: Decoder) throws {
// let container = try decoder.container(keyedBy: CodingKeys.self)
// wallets = (try? container.decode([RestoreCloudModule.RestoredBackup].self, forKey: .wallets)) ?? []
// watchlistIds = (try? container.decode([String].self, forKey: .watchlistIds)) ?? []
// contacts = try? container.decode([BackupContact].self, forKey: .contacts)
// evmSyncSources = try? container.decode(SyncSourceBackup.self, forKey: .evmSyncSources)
// settings = try? container.decode(SettingsBackup.self, forKey: .settings)
// }

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
if !wallets.isEmpty { try container.encode(wallets, forKey: .wallets) }
if !watchlistIds.isEmpty { try container.encode(watchlistIds, forKey: .watchlistIds) }
if let contacts { try container.encode(contacts, forKey: .contacts) }
if let settings { try container.encode(settings, forKey: .settings) }
try container.encode(version, forKey: .version)
try container.encode(version, forKey: .version)
try? container.encode(timestamp, forKey: .timestamp)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Foundation
import Chart
import CurrencyKit
import ThemeKit

struct SettingsBackup: Codable {
let evmSyncSources: EvmSyncSourceManager.SyncSourceBackup

let lockTimeEnabled: Bool
let remoteContactsSync: Bool
let defaultProviders: [DefaultProvider]
let chartIndicators: [ChartIndicator]
let indicatorsShown: Bool
let currentLanguage: String
let baseCurrency: String

let mode: ThemeMode
let showMarketTab: Bool
let launchScreen: LaunchScreen
let conversionTokenQueryId: String?
let balancePrimaryValue: BalancePrimaryValue
let balanceAutoHide: Bool
let appIcon: String

enum CodingKeys: String, CodingKey {
case evmSyncSources = "evm_sync_sources"
case lockTimeEnabled = "lock_time"
case remoteContactsSync = "contacts_sync"
case defaultProviders = "default_providers"
case chartIndicators = "indicators"
case indicatorsShown = "indicators_shown"
case currentLanguage = "language"
case baseCurrency = "currency"
case mode = "theme_mode"
case showMarketTab = "show_market"
case launchScreen = "launch_screen"
case conversionTokenQueryId = "conversion_token_query_id"
case balancePrimaryValue = "balance_primary_value"
case balanceAutoHide = "balance_auto_hide"
case appIcon = "app_icon"
}

}

extension SettingsBackup {
struct DefaultProvider: Codable {
enum CodingKeys: String, CodingKey {
case blockchainTypeId = "blockchain_type_id"
case provider
}

let blockchainTypeId: String
let provider: String
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

class WalletBackup: Codable {
let crypto: WalletBackupCrypto
let crypto: BackupCrypto
let id: String
let type: AccountType.Abstract
let isManualBackedUp: Bool
Expand All @@ -19,19 +19,19 @@ class WalletBackup: Codable {
case timestamp
}

init(crypto: WalletBackupCrypto, enabledWallets: [EnabledWallet], id: String, type: AccountType.Abstract, isManualBackedUp: Bool, version: Int, timestamp: TimeInterval) {
init(crypto: BackupCrypto, 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
self.version = version
self.timestamp = timestamp.rounded()
self.timestamp = timestamp
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
crypto = try container.decode(WalletBackupCrypto.self, forKey: .crypto)
crypto = try container.decode(BackupCrypto.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)
Expand Down
Loading

0 comments on commit 2cda645

Please sign in to comment.