Closed robbiehanson closed 3 years ago
Implementation Notes:
In Wallet.kt
there is a new function cloudKeyAndEncryptionNodeId()
. It derives a "cloudKey" for the wallet, which is then used to encrypt/decrypt the content that's synced to iCloud.
It also returns an "encrypted nodeID", which is just some string that is derived from the wallet, but which doesn't leak identifying information. We use this to segregate wallets within iCloud. (Each wallet gets its own cloud "container", which is essentially a separate database.)
These values are ultimately returned to the Swift layer via PhoenixBusiness.loadWallet()
There's a new SQLDelight file. The design is specific to Apple's CloudKit syncing requirements, so it's named accordingly. There's also a migrations file.
Note that this is shared between iOS & Android. Which means Android will create these tables too, even though it won't ever use them. I don't know if there's an easy way around this. More drastic measures might not be worth the risk. I assume that Android will eventually get its own dedicated tables too, and iOS will quietly create them, even though they're not needed. But it's a one-time operation.
In SyncManager.swift, there's a function serializeAndEncryptRow
which performs the encryption routine:
cborSerialize()
to get a blob for the incoming/outgoing paymentWhich means Android will create these tables too, even though it won't ever use them. I don't know if there's an easy way around this. More drastic measures might not be worth the risk.
I agree, I don't think there would be any easier solution than simply ignore this database when on Android 👍
Testing Notes:
If you already have an older version installed, then upgrading to this version means that the existing payment history will get automatically synced to the cloud.
If you disable/enable cloud sync (via Settings / Cloud backup), there is a 30 second timer that's used before the changes go into effect. The timer can be seen via the AppStatusPopover.
If you disable cloud sync, we delete the cloud container. (i.e. we delete the CloudKit RecordZone for the wallet) If you re-enable cloud sync, we automatically re-upload all existing transactions.
This branch increments the local database to v2 (via SQLDelight migration). If you try to re-install an older version (e.g. switch back to master, and build-and-go, without deleting app from simulator/device), then SQLDelight will crash the app.
If Phoenix exceeds the quota allowed on iCloud, what happens in the app?
For any kind of unusual error, the sync system executes exponential backoff. And the UI displays some useful information about the error:
The cloud backup setting screen should mention that only payments are uploaded and not the seed, to prevent any misunderstandings.
Done in d188ce7
Looks good!
Regarding the seed backup, I think we should mention after the You are responsible for backing up your seed
title that backing up the mnemonics yourself is the more secure option, if done correctly. Also in a future PR we will have to add a help ?
button like in the App Access
screen, that provides technical details about this feature and explains the trade-offs.
I think we should mention after the
You are responsible for backing up your seed
title that backing up the mnemonics yourself is the more secure option, if done correctly
Also in a future PR we will have to add a help
?
button like in theApp Access
screen, that provides technical details about this feature and explains the trade-offs.
That's a good idea :thumbsup:
Phoenix iOS can now optionally backup transactions in iCloud:
(This is a huge PR, so I'll be splitting this into multiple smaller PR's in the next few days.)
Overview:
We upload completed transactions to the user's iCloud.
More specifically, we use Apple's CloudKit, which provides us with a database-like API. The API is limited to {iCloud-user, application}. Which means only the logged-in iCloud user can access the database. And only our app can access our database(s).
We create a different "container" for each wallet. So each wallet is properly segregated. And mainnet/testnet segregation is handled too.
Security considerations:
1) The data stored in the cloud is encrypted, and can only be decrypted with the seed. We serialize each payment into a blob, and then we encrypt the blob before uploading it. The
cloudKey
used for encryption/decryption is derived from the seed using a derivation path. (The derivation path is different for mainnet vs testnet.)2) Each wallet gets its own segregated "cloud container". The name of the container is
Hash(cloudKey + nodeID)
. This prevents leakage of the user's nodeID.3) The size of a serialized
IncomingPayment
is generally smaller than anOutgoingPayment
. In order to obfuscate the payment type, we add padding to the serialized cleartext prior to encryption. The current algorithm is:4) The other known attack uses metadata & timestamps. By default, Alice's wallet syncs a payment to the cloud as soon as it completes. However, this associates Alice with a completed Lightning payment made at that time. Thus, Alice can opt-into "randomized upload delays", where the
SyncManager
injects a random delay automatically. In addition, this dely is exposed to Alice within the UI, and she can manually skip it if needed.Changes in the Kotlin layer:
expect
), and currently does nothing on Android.cloudkit_payments_queue
table in the database.WalletPayment
<->ByteArray
is done in Kotlin. And I've done a bunch of optimization here to minimize the size.Changes in iOS UI:
New options in the settings to control cloud backup:
The "cloud sync status" has been integrated into the AppStatusButton on the HomeScreen (upper left corner), as well as the AppStatusPopover:
The status is dynamic & interactive: