Final metadata encryption keys code & fixes

This commit is contained in:
Lukas Korba 2025-03-03 10:57:35 +01:00
parent ff6ea3e0fe
commit f2ab4c85c9
18 changed files with 201 additions and 115 deletions

View File

@ -309,7 +309,7 @@ extension SDKSynchronizerClient {
}
var clearedTxs: [TransactionState] = []
let latestBlockHeight = try await SDKSynchronizerClient.latestBlockHeight(synchronizer: synchronizer)
for clearedTransaction in clearedTransactions {

View File

@ -89,7 +89,7 @@ public extension UserMetadata {
static func encryptUserMetadata(_ umData: UserMetadata, account: Account) throws -> Data {
@Dependency(\.walletStorage) var walletStorage
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(),
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(account),
let umKey = encryptionKeys.getCached(account: account) else {
throw UserMetadataStorage.UMError.missingEncryptionKey
}
@ -131,7 +131,7 @@ public extension UserMetadata {
static func userMetadataFrom(encryptedData: Data, account: Account) throws -> UserMetadata? {
@Dependency(\.walletStorage) var walletStorage
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(),
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(account),
let umKey = encryptionKeys.getCached(account: account) else {
throw UserMetadataStorage.UMError.missingEncryptionKey
}
@ -151,32 +151,39 @@ public extension UserMetadata {
}
let encryptedSubData = try UserMetadata.subdata(of: encryptedData, in: offset..<encryptedData.count)
offset = 0
// Derive the sub-key for decrypting the user metadata.
let salt = encryptedSubData.prefix(upTo: 32)
let subKey = umKey.deriveEncryptionKey(salt: salt)
offset += 32
// Deserialize `metadata version`
let metadataVersionBytes = try UserMetadata.subdata(of: encryptedSubData, in: offset..<(offset + UserMetadataStorage.Constants.int64Size))
offset += UserMetadataStorage.Constants.int64Size
guard let metadataVersion = UserMetadata.bytesToInt(Array(metadataVersionBytes)) else {
return nil
}
let subKeys = umKey.deriveDecryptionKeys(salt: salt)
guard metadataVersion == UserMetadata.Constants.version else {
throw UserMetadataStorage.UMError.metadataVersionNotSupported
}
// Unseal the encrypted user metadata.
let sealed = try ChaChaPoly.SealedBox.init(combined: encryptedSubData.suffix(from: 32 + UserMetadataStorage.Constants.int64Size))
let data = try ChaChaPoly.open(sealed, using: subKey)
// deserialize the json's data
if let decodedUM = try? JSONDecoder().decode(UserMetadata.self, from: data) {
return decodedUM
for subKey in subKeys {
offset = 32
do {
// Deserialize `metadata version`
let metadataVersionBytes = try UserMetadata.subdata(of: encryptedSubData, in: offset..<(offset + UserMetadataStorage.Constants.int64Size))
offset += UserMetadataStorage.Constants.int64Size
guard let metadataVersion = UserMetadata.bytesToInt(Array(metadataVersionBytes)) else {
return nil
}
guard metadataVersion == UserMetadata.Constants.version else {
throw UserMetadataStorage.UMError.metadataVersionNotSupported
}
// Unseal the encrypted user metadata.
let sealed = try ChaChaPoly.SealedBox.init(combined: encryptedSubData.suffix(from: 32 + UserMetadataStorage.Constants.int64Size))
let data = try ChaChaPoly.open(sealed, using: subKey)
// deserialize the json's data
if let decodedUM = try? JSONDecoder().decode(UserMetadata.self, from: data) {
return decodedUM
}
} catch {
// this key failed to decrypt, try another one
}
}
return nil

View File

@ -45,12 +45,12 @@ public class UserMetadataStorage {
func filenameForEncryptedFile(account: Account) throws -> String {
@Dependency(\.walletStorage) var walletStorage
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(),
guard let encryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(account),
let umKey = encryptionKeys.getCached(account: account) else {
throw UMError.missingEncryptionKey
}
guard let filename = umKey.fileIdentifier() else {
guard let filename = umKey.fileIdentifier(account: account) else {
throw UMError.fileIdentifier
}
@ -97,7 +97,7 @@ public class UserMetadataStorage {
let encryptedUMData = try UserMetadata.encryptUserMetadata(metadata, account: account)
try encryptedUMData.write(to: fileURL, options: .atomic)
try encryptedUMData.write(to: fileURL)
@Dependency(\.remoteStorage) var remoteStorage
@ -132,10 +132,10 @@ public class UserMetadataStorage {
// Try to find and get the data from the encrypted file with the latest encryption version
let encryptedFileURL = documentsDirectory.appendingPathComponent(try filenameForEncryptedFile(account: account))
if FileManager.default.fileExists(atPath: encryptedFileURL.path) {
if !FileManager.default.fileExists(atPath: encryptedFileURL.path) {
throw UMError.localFileDoesntExist
}
if let encryptedUMData = try? Data(contentsOf: encryptedFileURL) {
return try UserMetadata.userMetadataFrom(encryptedData: encryptedUMData, account: account)
}
@ -201,7 +201,7 @@ public class UserMetadataStorage {
// MARK: - Bookmarking
public func isBookmarked(txId: String) -> Bool {
bookmarked[txId] != nil
bookmarked[txId]?.isBookmarked ?? false
}
public func toggleBookmarkFor(txId: String) {

View File

@ -19,9 +19,13 @@ public struct WalletStorage {
public enum Constants {
public static let zcashStoredWallet = "zcashStoredWallet"
public static let zcashStoredAdressBookEncryptionKeys = "zcashStoredAdressBookEncryptionKeys"
public static let zcashStoredUserMetadataEncryptionKeys = "zcashStoredUserMetadataEncryptionKeys"
public static let zcashStoredUserMetadataEncryptionKeys = "zcashStoredMetadataEncryptionKeys"
/// Versioning of the stored data
public static let zcashKeychainVersion = 1
public static func accountMetadataFilename(account: Account) -> String {
Constants.zcashStoredUserMetadataEncryptionKeys + "_\(account.name?.lowercased() ?? "")"
}
}
public enum KeychainError: Error, Equatable {
@ -150,6 +154,9 @@ public struct WalletStorage {
public func resetZashi() throws {
try deleteData(forKey: Constants.zcashStoredWallet)
try? deleteData(forKey: Constants.zcashStoredAdressBookEncryptionKeys)
try? deleteData(forKey: "\(Constants.zcashStoredUserMetadataEncryptionKeys)_zashi")
try? deleteData(forKey: "\(Constants.zcashStoredUserMetadataEncryptionKeys)_keystone")
}
public func importAddressBookEncryptionKeys(_ keys: AddressBookEncryptionKeys) throws {
@ -188,13 +195,13 @@ public struct WalletStorage {
return wallet
}
public func importUserMetadataEncryptionKeys(_ keys: UserMetadataEncryptionKeys) throws {
public func importUserMetadataEncryptionKeys(_ keys: UserMetadataEncryptionKeys, account: Account) throws {
do {
guard let data = try encode(object: keys) else {
throw KeychainError.encoding
}
try setData(data, forKey: Constants.zcashStoredUserMetadataEncryptionKeys)
try setData(data, forKey: Constants.accountMetadataFilename(account: account))
} catch KeychainError.duplicate {
throw WalletStorageError.alreadyImported
} catch {
@ -202,11 +209,11 @@ public struct WalletStorage {
}
}
public func exportUserMetadataEncryptionKeys() throws -> UserMetadataEncryptionKeys {
public func exportUserMetadataEncryptionKeys(account: Account) throws -> UserMetadataEncryptionKeys {
let reqData: Data?
do {
reqData = try data(forKey: Constants.zcashStoredUserMetadataEncryptionKeys)
reqData = try data(forKey: Constants.accountMetadataFilename(account: account))
} catch KeychainError.noDataFound {
throw WalletStorageError.uninitializedUserMetadataEncryptionKeys
} catch {

View File

@ -78,6 +78,6 @@ public struct WalletStorageClient {
public var importAddressBookEncryptionKeys: (AddressBookEncryptionKeys) throws -> Void
public var exportAddressBookEncryptionKeys: () throws -> AddressBookEncryptionKeys
public var importUserMetadataEncryptionKeys: (UserMetadataEncryptionKeys) throws -> Void
public var exportUserMetadataEncryptionKeys: () throws -> UserMetadataEncryptionKeys
public var importUserMetadataEncryptionKeys: (UserMetadataEncryptionKeys, Account) throws -> Void
public var exportUserMetadataEncryptionKeys: (Account) throws -> UserMetadataEncryptionKeys
}

View File

@ -45,11 +45,11 @@ extension WalletStorageClient: DependencyKey {
exportAddressBookEncryptionKeys: {
try walletStorage.exportAddressBookEncryptionKeys()
},
importUserMetadataEncryptionKeys: { keys in
try walletStorage.importUserMetadataEncryptionKeys(keys)
importUserMetadataEncryptionKeys: { keys, account in
try walletStorage.importUserMetadataEncryptionKeys(keys, account: account)
},
exportUserMetadataEncryptionKeys: {
try walletStorage.exportUserMetadataEncryptionKeys()
exportUserMetadataEncryptionKeys: { account in
try walletStorage.exportUserMetadataEncryptionKeys(account: account)
}
)
}

View File

@ -33,7 +33,7 @@ extension WalletStorageClient {
resetZashi: { },
importAddressBookEncryptionKeys: { _ in },
exportAddressBookEncryptionKeys: { .empty },
importUserMetadataEncryptionKeys: { _ in },
exportUserMetadataEncryptionKeys: { .empty }
importUserMetadataEncryptionKeys: { _, _ in },
exportUserMetadataEncryptionKeys: { _ in .empty }
)
}

View File

@ -167,7 +167,7 @@ extension Root {
case .tabs, .initialization, .onboarding, .updateStateAfterConfigUpdate, .alert, .phraseDisplay, .synchronizerStateChanged,
.welcome, .binding, .resetZashiSDKFailed, .resetZashiSDKSucceeded, .resetZashiKeychainFailed, .resetZashiKeychainRequest, .resetZashiFinishProcessing, .resetZashiKeychainFailedWithCorruptedData, .debug, .walletConfigLoaded, .exportLogs, .confirmationDialog,
.notEnoughFreeSpace, .serverSetup, .serverSetupBindingUpdated, .batteryStateChanged, .cancelAllRunningEffects, .addressBookBinding, .addressBook, .addressBookContactBinding, .addressBookAccessGranted, .osStatusError, .observeTransactions, .foundTransactions, .minedTransaction, .fetchTransactionsForTheSelectedAccount, .fetchedTransactions, .noChangeInTransactions, .loadContacts, .contactsLoaded, .loadUserMetadata:
.notEnoughFreeSpace, .serverSetup, .serverSetupBindingUpdated, .batteryStateChanged, .cancelAllRunningEffects, .addressBookBinding, .addressBook, .addressBookContactBinding, .addressBookAccessGranted, .osStatusError, .observeTransactions, .foundTransactions, .minedTransaction, .fetchTransactionsForTheSelectedAccount, .fetchedTransactions, .noChangeInTransactions, .loadContacts, .contactsLoaded, .loadUserMetadata, .resolveMetadataEncryptionKeys:
return .none
}
}

View File

@ -319,51 +319,34 @@ extension Root {
let walletAccounts = try await sdkSynchronizer.walletAccounts()
await send(.initialization(.loadedWalletAccounts(walletAccounts)))
await send(.resolveMetadataEncryptionKeys)
try await sdkSynchronizer.start(false)
var selectedAccount: WalletAccount?
for account in walletAccounts {
if account.vendor == .zcash {
selectedAccount = account
break
}
}
if let account = selectedAccount {
let addressBookEncryptionKeys = try? walletStorage.exportAddressBookEncryptionKeys()
if addressBookEncryptionKeys == nil {
var keys = AddressBookEncryptionKeys.empty
try keys.cacheFor(
seed: seedBytes,
account: account.account,
network: zcashSDKEnvironment.network.networkType
)
do {
var keys = AddressBookEncryptionKeys.empty
try keys.cacheFor(
seed: seedBytes,
account: account.account,
network: zcashSDKEnvironment.network.networkType
)
try walletStorage.importAddressBookEncryptionKeys(keys)
} catch {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
}
let userMetadataEncryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys()
if userMetadataEncryptionKeys == nil {
var keys = UserMetadataEncryptionKeys.empty
try keys.cacheFor(
seed: seedBytes,
account: account.account,
network: zcashSDKEnvironment.network.networkType
)
do {
try walletStorage.importUserMetadataEncryptionKeys(keys)
} catch {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
}
let uAddress = try? await sdkSynchronizer.getUnifiedAddress(account.id)
await send(.initialization(.initializationSuccessfullyDone(uAddress)))
} else {
@ -410,7 +393,45 @@ extension Root {
)
case .tabs(.addKeystoneHWWallet(.loadedWalletAccounts)), .tabs(.settings(.integrations(.addKeystoneHWWallet(.loadedWalletAccounts)))):
return .send(.loadUserMetadata)
return .merge(
.send(.resolveMetadataEncryptionKeys),
.send(.loadUserMetadata)
)
case .resolveMetadataEncryptionKeys:
do {
let storedWallet: StoredWallet
do {
storedWallet = try walletStorage.exportWallet()
} catch {
return .send(.destination(.updateDestination(.osStatusError)))
}
try mnemonic.isValid(storedWallet.seedPhrase.value())
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
return .run { [walletAccounts = state.walletAccounts] send in
do {
for account in walletAccounts {
let userMetadataEncryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(account.account)
if userMetadataEncryptionKeys == nil {
do {
var keys = UserMetadataEncryptionKeys.empty
try keys.cacheFor(
seed: seedBytes,
account: account.account,
network: zcashSDKEnvironment.network.networkType
)
try walletStorage.importUserMetadataEncryptionKeys(keys, account.account)
} catch {
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
}
}
}
}
}
} catch { }
return .none
case .initialization(.checkBackupPhraseValidation):
let storedWallet: StoredWallet

View File

@ -185,6 +185,7 @@ public struct Root {
// UserMetadata
case loadUserMetadata
case resolveMetadataEncryptionKeys
}
@Dependency(\.addressBook) var addressBook

View File

@ -211,7 +211,7 @@ extension TransactionDetailsView {
.frame(width: 51, height: 51)
Asset.Assets.Icons.shieldTickFilled.image
.zImage(size: 24, style: Design.Text.tertiary)
.zImage(size: 24, style: Design.Text.primary)
}
}
.offset(x: -4)
@ -243,7 +243,7 @@ extension TransactionDetailsView {
if store.isSensitiveContentHidden {
Text(L10n.General.hideBalancesMost)
} else {
Text(store.transaction.zecAmount.decimalString())
Text(store.transaction.netValue)
+ Text(" \(tokenName)")
.foregroundColor(Design.Text.quaternary.color(colorScheme))
}

View File

@ -98,10 +98,11 @@ extension TransactionsManagerView {
FilterView(title: L10n.Filter.notes, active: store.isNotesFilterActive) { store.send(.toggleFilter(.notes)) }
FilterView(title: L10n.Filter.bookmarked, active: store.isBookmarkedFilterActive) { store.send(.toggleFilter(.bookmarked)) }
}
HStack(spacing: 8) {
FilterView(title: L10n.Filter.contact, active: store.isContactFilterActive) { store.send(.toggleFilter(.contact)) }
}
// Hidden for now but possibly released in the near future
// HStack(spacing: 8) {
// FilterView(title: L10n.Filter.contact, active: store.isContactFilterActive) { store.send(.toggleFilter(.contact)) }
// }
}
.padding(.bottom, 32)

View File

@ -40,6 +40,8 @@ public struct TransactionState: Equatable, Identifiable {
public var isMarkedAsRead = false
public var isInAddressBook = false
public var hasTransparentOutputs = false
public var totalSpent: Zatoshi?
public var totalReceived: Zatoshi?
public var rawID: Data? = nil
@ -198,6 +200,12 @@ public struct TransactionState: Equatable, Identifiable {
public var totalAmount: Zatoshi {
Zatoshi(zecAmount.amount + (fee?.amount ?? 0))
}
public var netValue: String {
isShieldingTransaction
? Zatoshi(totalSpent?.amount ?? 0).decimalString()
: zecAmount.decimalString()
}
public init(
errorMessage: String? = nil,
@ -272,6 +280,8 @@ extension TransactionState {
isTransparentRecipient = false
self.hasTransparentOutputs = hasTransparentOutputs
memoCount = transaction.memoCount
totalSpent = transaction.totalSpent
totalReceived = transaction.totalReceived
// TODO: [#1313] SDK improvements so a client doesn't need to determing if the transaction isPending
// https://github.com/zcash/ZcashLightClientKit/issues/1313

View File

@ -18,17 +18,29 @@ public struct UserMetadataEncryptionKeys: Codable, Equatable {
public static let version = 1
}
var keys: [Int: UserMetadataKey]
var keys: [Int: UserMetadataKeys]
public mutating func cacheFor(seed: [UInt8], account: Account, network: NetworkType) throws{
public mutating func cacheFor(seed: [UInt8], account: Account, network: NetworkType) throws {
guard let zip32AccountIndex = account.hdAccountIndex else {
return
}
keys[Int(zip32AccountIndex.index)] = try UserMetadataKey(seed: seed, account: account, network: network)
guard let info = "metadata".data(using: .utf8) else {
fatalError("Unable to prepare `info` info")
}
let metadataKey = try AccountMetadataKey(
from: seed,
accountIndex: zip32AccountIndex,
networkType: network
)
let privateMetadataKeys = try metadataKey.derivePrivateUseMetadataKey(ufvk: account.ufvk, privateUseSubject: [UInt8](info))
keys[Int(zip32AccountIndex.index)] = UserMetadataKeys(privateKeys: privateMetadataKeys)
}
public func getCached(account: Account) -> UserMetadataKey? {
public func getCached(account: Account) -> UserMetadataKeys? {
guard let zip32AccountIndex = account.hdAccountIndex else {
return nil
}
@ -43,19 +55,25 @@ extension UserMetadataEncryptionKeys {
)
}
public struct UserMetadataKey: Codable, Equatable, Redactable {
let key: SymmetricKey
public struct UserMetadataKeys: Codable, Equatable, Redactable {
let keys: [SymmetricKey]
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
key = SymmetricKey(data: try container.decode(Data.self))
keys = try container.decode([Data].self).map { SymmetricKey(data: $0) }
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try key.withUnsafeBytes { key in
let key = Data(key)
try container.encode(key)
do {
let privateKeys = keys.map { symmetricKey in
symmetricKey.withUnsafeBytes { key in
return Data(key)
}
}
try container.encode(privateKeys)
} catch {
fatalError("Unable to encode `UserMetadataKeys`")
}
}
@ -67,21 +85,8 @@ public struct UserMetadataKey: Codable, Equatable, Redactable {
* control requirements for the seed phrase and the user metadata, this key
* should be cached in the app's keystore.
*/
public init(seed: [UInt8], account: Account, network: NetworkType) throws {
let zip32AccountIndex: Zip32AccountIndex
if let zip32AccountIndexUnwrapped = account.hdAccountIndex {
zip32AccountIndex = zip32AccountIndexUnwrapped
} else {
zip32AccountIndex = Zip32AccountIndex(0)
}
self.key = try SymmetricKey(data: DerivationToolClient.live().deriveArbitraryAccountKey(
[UInt8]("ZashiAddressBookEncryptionV1".utf8),
seed,
zip32AccountIndex,
network
))
public init(privateKeys: [Data]) {
keys = privateKeys.map { SymmetricKey(data: $0) }
}
/**
@ -99,26 +104,60 @@ public struct UserMetadataKey: Codable, Equatable, Redactable {
fatalError("Unable to prepare `encryption_key` info")
}
return HKDF<SHA256>.deriveKey(inputKeyMaterial: key, info: salt + info, outputByteCount: 32)
guard let firstKey = keys.first else {
fatalError("Unable to process `firstKey`")
}
return HKDF<SHA256>.deriveKey(inputKeyMaterial: firstKey, info: salt + info, outputByteCount: 32)
}
/**
* Derives a one-time user metadata decryption keys.
*
* At decryption time, the one-time property MUST be ensured by generating a
* random 32-byte salt.
*/
public func deriveDecryptionKeys(
salt: Data
) -> [SymmetricKey] {
assert(salt.count == 32)
guard let info = "encryption_key".data(using: .utf8) else {
fatalError("Unable to prepare `encryption_key` info")
}
var decryptionKeys: [SymmetricKey] = []
keys.forEach {
decryptionKeys.append(HKDF<SHA256>.deriveKey(inputKeyMaterial: $0, info: salt + info, outputByteCount: 32))
}
return decryptionKeys
}
/**
* Derives the filename that this key is able to decrypt.
*/
public func fileIdentifier() -> String? {
public func fileIdentifier(account: Account) -> String? {
guard let info = "file_identifier".data(using: .utf8) else {
fatalError("Unable to prepare `file_identifier` info")
}
guard let firstKey = keys.first else {
fatalError("Unable to process `firstKey`")
}
// Perform HKDF with SHA-256
let hkdfKey = HKDF<SHA256>.deriveKey(inputKeyMaterial: key, info: info, outputByteCount: 32)
let hkdfKey = HKDF<SHA256>.deriveKey(inputKeyMaterial: firstKey, info: info, outputByteCount: 32)
// Convert the HKDF output to a hex string
let fileIdentifier = hkdfKey.withUnsafeBytes { rawBytes in
rawBytes.map { String(format: "%02x", $0) }.joined()
}
let prefix = "\(account.name?.lowercased() ?? "")"
// Prepend the prefix to the result
return "zashi-user-metadata-\(fileIdentifier)"
return "\(prefix)-metadata-\(fileIdentifier)"
}
}

View File

@ -118,7 +118,7 @@ public struct TransactionRowView: View {
Text(L10n.General.hideBalancesMost)
} else {
Text(transaction.isSpending ? "- " : "")
+ Text(transaction.zecAmount.decimalString())
+ Text(transaction.netValue)
+ Text(" \(tokenName)")
}
}

View File

@ -2514,7 +2514,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
@ -2527,7 +2527,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.4.11;
MARKETING_VERSION = 0.5.0;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2545,7 +2545,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_BITCODE = NO;
@ -2557,7 +2557,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.4.11;
MARKETING_VERSION = 0.5.0;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2575,7 +2575,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "zashi-internal.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\"";
DEVELOPMENT_TEAM = RLPRR8CPQG;
ENABLE_BITCODE = NO;
@ -2587,7 +2587,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.4.11;
MARKETING_VERSION = 0.5.0;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-testnet";
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@ -554,7 +554,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
"state" : {
"revision" : "668571f280e0a9c8a664ca2d948b23d71c8de896"
"revision" : "5182c7a77759c07708d3e5d1120586bca3de832a"
}
},
{
@ -572,7 +572,7 @@
"location" : "https://github.com/LukasKorba/ZcashLightClientKit",
"state" : {
"branch" : "preview-ffi-0.13.0",
"revision" : "01f24d8e00260700c9a3841ac77dc867bd6f6bf0"
"revision" : "630e24ec8714d499916989760f1c4c91db68d190"
}
}
],

View File

@ -55,7 +55,7 @@
<key>NSUbiquitousContainerIsDocumentStorage</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>Zashi Address Book</string>
<string>Zashi Internal</string>
</dict>
</dict>
<key>UIAppFonts</key>