Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement classes for full backup #5252

Merged
merged 1 commit into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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