Skip to content

Commit

Permalink
Refactor backup and crypto structures.
Browse files Browse the repository at this point in the history
- Extract many small functions from encrypt/decrypt, refactor names and logic for compose full backups
  • Loading branch information
ant013 committed Oct 9, 2023
1 parent ee0e75c commit db3495d
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 319 deletions.
6 changes: 6 additions & 0 deletions UnstoppableWallet/UnstoppableWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,7 @@
ABC9A36297D869E49C152CAB /* SwapRevokeConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A1360FE305343B1049CF /* SwapRevokeConfirmationViewController.swift */; };
ABC9A36D3A4EEABF6EA6DBA0 /* Shake.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A104D916039D690E454E /* Shake.swift */; };
ABC9A372F53F1F1D59BF8969 /* RestoreFileConfigurationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9ADA345301F29B947F281 /* RestoreFileConfigurationModule.swift */; };
ABC9A37FB71FA7DA14553EFC /* RawFullBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A819E6708797C571CA0B /* RawFullBackup.swift */; };
ABC9A394D1D0D165160F5F43 /* RestoreFileConfigurationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD9A0F91C825D149B289 /* RestoreFileConfigurationService.swift */; };
ABC9A395A96C1F7C30F21940 /* WalletConnectPendingRequestsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AD3F677671FB57CCD886 /* WalletConnectPendingRequestsService.swift */; };
ABC9A3B155B3F6E7E0F2CB07 /* HudHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A381CB4C09FF7CB62A94 /* HudHelper.swift */; };
Expand Down Expand Up @@ -2403,6 +2404,7 @@
ABC9AEF62C857F322FFA87E4 /* ContactBookAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AC8CCF3B57FDFC817356 /* ContactBookAddressService.swift */; };
ABC9AF04946C86FA6DBD4225 /* RestorePassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AE8D5944EB202A471C80 /* RestorePassphraseViewModel.swift */; };
ABC9AF1729BA19223BB39E06 /* SendEip721ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A7C3BC5FC664BBF14C4F /* SendEip721ViewModel.swift */; };
ABC9AF309AAE5C54D2020B23 /* RawFullBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A819E6708797C571CA0B /* RawFullBackup.swift */; };
ABC9AF371FBB4BEA654A78B6 /* MetadataMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AA2491ADC4E5E089CD42 /* MetadataMonitor.swift */; };
ABC9AF4D82ACDFBFBDC2D23C /* MarketCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9A37521CD6E2CC5BA4E68 /* MarketCardView.swift */; };
ABC9AF5B0B1D5FE002288AE1 /* FileStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABC9AB0A37663BC3F17C7A81 /* FileStorage.swift */; };
Expand Down Expand Up @@ -3882,6 +3884,7 @@
ABC9A80143F95E28346C81FE /* SendMemoInputService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendMemoInputService.swift; sourceTree = "<group>"; };
ABC9A806FD17A129212E3F7C /* NftAssetOverviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NftAssetOverviewViewController.swift; sourceTree = "<group>"; };
ABC9A8080797194017F736AB /* ContactBookContactViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactBookContactViewModel.swift; sourceTree = "<group>"; };
ABC9A819E6708797C571CA0B /* RawFullBackup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawFullBackup.swift; sourceTree = "<group>"; };
ABC9A82A1E9AE6CC0E24756B /* SendNftModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendNftModule.swift; sourceTree = "<group>"; };
ABC9A830FE79DBF62FD63CC4 /* ThemeMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeMode.swift; sourceTree = "<group>"; };
ABC9A845B2969166028BA5F0 /* WalletConnectAppShowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectAppShowView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7230,6 +7233,7 @@
ABC9AECEEB35D57CB0965E79 /* WalletBackup.swift */,
ABC9A41F6AA0B65FDA91EB68 /* FullBackup.swift */,
ABC9AA7FC181E0E0FB74BEF5 /* SettingsBackup.swift */,
ABC9A819E6708797C571CA0B /* RawFullBackup.swift */,
);
path = Crypto;
sourceTree = "<group>";
Expand Down Expand Up @@ -9459,6 +9463,7 @@
ABC9A904FCE6BFE793C944AE /* RestoreFileConfigurationModule.swift in Sources */,
ABC9A394D1D0D165160F5F43 /* RestoreFileConfigurationService.swift in Sources */,
ABC9A481F1C13DBAAD3F632B /* RestoreFileConfigurationViewModel.swift in Sources */,
ABC9AF309AAE5C54D2020B23 /* RawFullBackup.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -10785,6 +10790,7 @@
ABC9A372F53F1F1D59BF8969 /* RestoreFileConfigurationModule.swift in Sources */,
ABC9A2A327AF3D72F9842DCA /* RestoreFileConfigurationService.swift in Sources */,
ABC9A2F6D2A2AAFA31C64BAB /* RestoreFileConfigurationViewModel.swift in Sources */,
ABC9A37FB71FA7DA14553EFC /* RawFullBackup.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
14 changes: 2 additions & 12 deletions UnstoppableWallet/UnstoppableWallet/Core/Crypto/BackupCrypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class BackupCrypto: Codable {
}

extension BackupCrypto {
func data(passphrase: String) throws -> Data {
func decrypt(passphrase: String) throws -> Data {
try Self.validate(passphrase: passphrase)
// Validation data
guard let data = Data(base64Encoded: cipherText) else {
Expand All @@ -76,16 +76,6 @@ extension BackupCrypto {
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 {
Expand All @@ -104,7 +94,7 @@ extension BackupCrypto {
}
}

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

let cipherText = try BackupCryptoHelper.AES128(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct FullBackup {
let wallets: [RestoreCloudModule.RestoredBackup]
let watchlistIds: [String]
let contacts: BackupCrypto?
let settings: SettingsBackup?
let settings: SettingsBackup
let version: Int
let timestamp: TimeInterval?
}
Expand All @@ -27,7 +27,7 @@ extension FullBackup: Codable {
wallets = (try? container.decode([RestoreCloudModule.RestoredBackup].self, forKey: .wallets)) ?? []
watchlistIds = (try? container.decode([String].self, forKey: .watchlistIds)) ?? []
contacts = try? container.decode(BackupCrypto.self, forKey: .contacts)
settings = try? container.decode(SettingsBackup.self, forKey: .settings)
settings = try container.decode(SettingsBackup.self, forKey: .settings)
version = try container.decode(Int.self, forKey: .version)
timestamp = try? container.decode(TimeInterval.self, forKey: .timestamp)
}
Expand All @@ -38,7 +38,7 @@ extension FullBackup: Codable {
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(settings, forKey: .settings)
try container.encode(version, forKey: .version)
try? container.encode(timestamp, forKey: .timestamp)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

struct RawFullBackup {
var accounts: [RawWalletBackup]
let watchlistIds: [String]
let contacts: [BackupContact]
let settings: SettingsBackup
let customSyncSources: [EvmSyncSourceRecord]
}

struct RawWalletBackup {
let account: Account
let enabledWallets: [WalletBackup.EnabledWallet]
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import CurrencyKit
import ThemeKit

struct SettingsBackup: Codable {
let evmSyncSources: EvmSyncSourceManager.SyncSourceBackup
var evmSyncSources: EvmSyncSourceManager.SyncSourceBackup
let btcModes: [BtcBlockchainManager.BtcRestoreModeBackup]

let lockTimeEnabled: Bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class CloudBackupManager {
let oneWalletItemsV2: [String: RestoreCloudModule.RestoredBackup] = try Self.downloadItems(url: url, fileStorage: fileStorage, logger: logger)
let mapped = oneWalletItemsV2.reduce(into: [:]) { $0[$1.value.name] = $1.value.walletBackup }

oneWalletItems.merge(mapped) { backup, backup2 in backup2 }
oneWalletItems.merge(mapped) { _, backup2 in backup2 }
let fullBackupItems: [String: FullBackup] = try Self.downloadItems(url: url, fileStorage: fileStorage, logger: logger)

state = .success
Expand Down Expand Up @@ -159,8 +159,9 @@ extension CloudBackupManager {
}

func save(account: Account, passphrase: String, name: String) throws {
let backup = try appBackupProvider.walletBackup(
let backup = try AppBackupProvider.encrypt(
account: account,
wallets: appBackupProvider.enabledWallets(account: account),
passphrase: passphrase
)

Expand All @@ -173,16 +174,14 @@ extension CloudBackupManager {
}
}

private func data(fields: [AppBackupProvider.Field], passphrase: String) throws -> Data {
let backup = try appBackupProvider.fullBackup(
fields: fields,
passphrase: passphrase
)
private func data(accountIds: [String], passphrase: String) throws -> Data {
let rawBackup = appBackupProvider.fullBackup(accountIds: accountIds)
let backup = try appBackupProvider.encrypt(raw: rawBackup, passphrase: passphrase)
return try JSONEncoder().encode(backup)
}

func file(fields: [AppBackupProvider.Field], passphrase: String, name: String) throws -> URL {
let data = try data(fields: fields, passphrase: passphrase)
func file(accountIds: [String], passphrase: String, name: String) throws -> URL {
let data = try data(accountIds: accountIds, passphrase: passphrase)

// save book to temporary file
guard let temporaryFileUrl = ContactBookManager.localUrl?.appendingPathComponent(name + ".json") else {
Expand All @@ -193,9 +192,9 @@ extension CloudBackupManager {
return temporaryFileUrl
}

func save(fields: [AppBackupProvider.Field], passphrase: String, name: String) throws {
func save(accountIds: [String], passphrase: String, name: String) throws {
do {
let encoded = try data(fields: fields, passphrase: passphrase)
let encoded = try data(accountIds: accountIds, passphrase: passphrase)
try save(encoded: encoded, name: name)
} catch {
logger?.log(level: .debug, message: "CloudAccountManager.downloadItems, can't save \(name). Because: \(error)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MarketKit
import HsToolKit

class EvmBlockchainManager {
private let blockchainTypes: [BlockchainType] = [
static let blockchainTypes: [BlockchainType] = [
.ethereum,
.binanceSmartChain,
.polygon,
Expand All @@ -24,7 +24,7 @@ class EvmBlockchainManager {

var allBlockchains: [Blockchain] {
do {
return try marketKit.blockchains(uids: blockchainTypes.map { $0.uid })
return try marketKit.blockchains(uids: EvmBlockchainManager.blockchainTypes.map { $0.uid })
} catch {
return []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,54 +286,66 @@ extension EvmSyncSourceManager {
}

extension EvmSyncSourceManager {
func backup(passphrase: String) -> SyncSourceBackup {
let customSources = ((try? evmSyncSourceStorage.getAll()) ?? [])
.map { record in
let crypto = record.auth
.flatMap { $0.isEmpty ? nil : $0 }
.flatMap { $0.data(using: .utf8) }
.flatMap { try? BackupCrypto.instance(data: $0, passphrase: passphrase) }

return CustomSyncSource(
blockchainTypeUid: record.blockchainTypeUid,
url: record.url,
auth: crypto
)
}
let selected = BlockchainType
.supported
.filter { !$0.allowedProviders.isEmpty }
var customSources: [EvmSyncSourceRecord] {
(try? evmSyncSourceStorage.getAll()) ?? []
}

var selectedSources: [SelectedSource] {
EvmBlockchainManager
.blockchainTypes
.map { type in
SelectedSource(
blockchainTypeUid: type.uid,
url: syncSource(blockchainType: type).rpcSource.url.absoluteString
)
}
return .init(selected: selected, custom: customSources)
}
}

func restore(backup: SyncSourceBackup, passphrase: String? = nil) {
var blockchainTypes = Set<BlockchainType>()
backup.custom.forEach { source in
let auth: String? = source.auth.flatMap {
guard let passphrase else { return nil }
return try? $0.data(passphrase: passphrase)
}
extension EvmSyncSourceManager {
func decrypt(sources: [CustomSyncSource], passphrase: String) throws -> [EvmSyncSourceRecord] {
try sources.map { source in
let auth = try source.auth
.flatMap { try $0.decrypt(passphrase: passphrase) }
.flatMap { String(data: $0, encoding: .utf8) }
let record = EvmSyncSourceRecord(

return EvmSyncSourceRecord(
blockchainTypeUid: source.blockchainTypeUid,
url: source.url,
auth: auth
)
}
}

func encrypt(sources: [EvmSyncSourceRecord], passphrase: String) throws -> [CustomSyncSource] {
try sources.map { source in
let crypto = try source.auth
.flatMap { $0.isEmpty ? nil : $0 }
.flatMap { $0.data(using: .utf8) }
.flatMap { try BackupCrypto.encrypt(data: $0, passphrase: passphrase) }

return CustomSyncSource(
blockchainTypeUid: source.blockchainTypeUid,
url: source.url,
auth: crypto
)
}
}
}

extension EvmSyncSourceManager {
func restore(selected: [SelectedSource], custom: [EvmSyncSourceRecord]) {
var blockchainTypes = Set<BlockchainType>()
custom.forEach { source in
blockchainTypes.insert(BlockchainType(uid: source.blockchainTypeUid))
try? evmSyncSourceStorage.save(record: record)
try? evmSyncSourceStorage.save(record: source)
}

backup.selected.forEach { source in
selected.forEach { source in
let blockchainType = BlockchainType(uid: source.blockchainTypeUid)
if let syncSource = allSyncSources(blockchainType: blockchainType)
.first(where: { $0.rpcSource.url.absoluteString == source.url }) {
.first(where: { $0.rpcSource.url.absoluteString == source.url })
{
saveCurrent(syncSource: syncSource, blockchainType: blockchainType)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,14 @@ extension ContactBookManager {
}

extension ContactBookManager {
static func encode(crypto: BackupCrypto, passphrase: String) throws -> [BackupContact] {
let data = try crypto.data(passphrase: passphrase)
static func encrypt(contacts: [BackupContact], passphrase: String) throws -> BackupCrypto {
let encoder = JSONEncoder()
let data = try encoder.encode(contacts)
return try BackupCrypto.encrypt(data: data, passphrase: passphrase)
}

static func decrypt(crypto: BackupCrypto, passphrase: String) throws -> [BackupContact] {
let data = try crypto.decrypt(passphrase: passphrase)
let decoder = JSONDecoder()
let contacts = try decoder.decode([BackupContact].self, from: data)

Expand Down
Loading

0 comments on commit db3495d

Please sign in to comment.