:: ZIP: 315 Title: Best Practices for Wallet Handling of Multiple Pools Owners: Daira Emma Hopwood Jack Grigg Status: Proposed Category: Wallet Discussions-To: Pull-Request: Terminology =========== The key words "MUST", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119. [#RFC2119]_ The terms below are to be interpreted as follows: Auto-shielding The process of automatically transferring transparent funds to the most recent shielded pool on receipt. Auto-migration The process of automatically transferring shielded funds from older pools to the most preferred (usually the most recent) pool on receipt. Opportunistic shielding TODO Opportunistic migration TODO Confirmed TODO Partially confirmed TODO Transaction output (TXO) An output (transparent TXO or note) of a transaction on the consensus block chain or in the mempool visible to a wallet. Motivation ========== The aim of this ZIP is to provide wallet developers with a set of best practices by which they can minimize the leakage of information when constructing transactions. This includes best practices for: * how to handle interactions between the ZIP 32 key tree and Unified Addresses; * when to use external or internal keys/addresses; * sharing addresses and viewing keys; * sending and receiving funds; * migrating funds between pools. Requirements ============ User consent ------------ The guiding principle of these requirements is that users must explicitly consent to each instance of information being revealed by the wallet. A wallet typically reveals some information in the process of creating a transaction. Which information is revealed depends on the configured wallet privacy policy. A user may give blanket consent to reveal a particular kind of information, and must also be able to change the configured wallet privacy policy to avoid the wallet creating new privacy leaks of a given type. The specifications below describe some situations in which blanket consent may be inappropriate. Some varieties of consent may not be revocable, for example if a user chooses to share some of their keys. Prompt accessibility of funds ----------------------------- Wallets need to take account of two concerns: * enabling funds to be spent as quickly as possible to reduce latency; * waiting for long enough before spending TXOs to ensure that the spendable balance is not overestimated, and so can be trusted by the user. To enable this we define two kinds of TXOs: * An untrusted TXO is a TXO received from a party that may try to double-spend. * A trusted TXO is a TXO received from a party where the wallet trusts a double-spend not to occur, e.g. TXOs created by the wallet's internal TXO handling. Wallets can then require that untrusted TXOs need more confirmations before they become spendable than trusted TXOs. This provides an improved trade-off between latency on the one hand, and reliability and safety on the other. Specification ============= Long-term storage of funds -------------------------- It is RECOMMENDED that wallets only hold funds as shielded in the long-term; that is, they automatically shield incoming transparent funds. The remainder of this specification assumes a wallet that follows this recommendation, except where explicitly noted. Trusted and untrusted TXOs -------------------------- A wallet SHOULD have a policy that is clearly communicated to the user for the number of confirmations needed to spend untrusted and trusted TXOs respectively. The following confirmation policy is RECOMMENDED: * 10 confirmations, for untrusted TXOs; * 3 confirmations, for trusted TXOs. Wallets MAY enable users to mark specific external transactions as trusted, allowing their received TXOs to be treated as trusted TXOs even if they do not have the required number of confirmations. Categories of TXOs according to spendability -------------------------------------------- A TXO is *confirmed spendable* if and only if the wallet has its spending key, and: * it is a trusted TXO that has at least the required confirmations for trusted TXOs; or * it is an untrusted TXO that has at least the required confirmations for untrusted TXOs. A TXO is *unconfirmed spendable* if and only if the wallet has its spending key, and it is not confirmed spendable. A TXO is *watch-only* if and only if the wallet has its full viewing key (or address in the case of a transparent TXO) but not its spending key. Reporting of balances --------------------- Wallets SHOULD report: * Spendable balance. * Pending balance, *or* total balance. These are calculated as follows: * The spendable balance is the sum of values of confirmed spendable TXOs. * The pending balance is the sum of values of unconfirmed spendable TXOs. * The total balance is the spendable balance plus the pending balance. Reporting of transactions ------------------------- If a transaction contains both spendable and watch-only TXOs, its spendable and watch-only incoming balances MUST be reported separately. Incoming transactions ''''''''''''''''''''' A transaction is incoming if it contains unconfirmed TXOs. Incoming transactions SHOULD be reported with their number of confirmations and their balances as described in `Reporting of balances`_. Sent transactions ''''''''''''''''' A transaction is sent if it was either: * created by the wallet, or * detected using the wallet's outgoing viewing keys, or * detected as having been sent from one of the wallet's watch-only addresses. Sent transactions SHOULD be reported with their number of confirmations, how long until they expire, and their balances as described in `Reporting of balances`_. Auto-shielding -------------- If they use auto-shielding, then any transparent balance should be treated as pending. For wallets that allow long-term storage of transparent funds, they SHOULD also show spendable transparent and pending (or total) transparent according to the same policy. Unedited -------- Wallets MUST report at least separate shielded and transparent balance. If auto-shielding or auto-migration is off, then wallets MAY report separate balances for each shielded pool and for transparent balance. If the wallet never supports a given pool, it can obviously omit balances for that pool. If auto-shielding is on, transparent funds should be reported in "balance unavailable to spend". Wallets SHOULD separately report the balances of funds that are immediately spendable, and any remaining funds that are expected from unconfirmed or partially confirmed transfers. TODO: make this more precise in terms of the following categories: * Funds at rest (not involved in any not-fully-confirmed transfer) * Outgoing funds to an external source (might come back if the tx doesn't go through) * Incoming funds from an external source * Funds we are sending to ourself. Rationale ''''''''' The specification of balance reporting is intended to give the user visibility into the operation of auto-shielding, opportunistic shielding, and pool migration/usage. Linkability of transactions or addresses ---------------------------------------- Network-layer privacy --------------------- Viewing keys ------------ What they are supposed to reveal; see ZIP 310 for Sapling (needs updating for Orchard). https://github.com/zcash/zips/issues/606 Allowed transfers ----------------- * Sprout -> transparent or Sapling * Sapling -> transparent or Sapling or Orchard * Orchard -> transparent or Sapling or Orchard * if auto-shielding is off: * transparent -> transparent or Sapling or Orchard * if auto-shielding is on: * transparent -> internal Orchard or Sapling Note: wallets MAY further restrict the set of transfers they perform. Auto-shielding -------------- Wallets SHOULD NOT spend funds from a transparent address to an external address, unless the user gives explicit consent for this on a per-transaction basis. In order to support this policy, wallets SHOULD implement a system of auto-shielding with the following characteristics. If auto-shielding functionality is available in a wallet, then users MUST be able to explicitly consent to one of the following possibilities: * auto-shielding is always on; * auto-shielding is always off; * the user specifies a policy... Auto-shielding MUST be one of: * "must opt in or out" (zcashd will do this -- i.e. refuse to start unless the option is configured), or * always on. Auto-migration -------------- Information leakage for transfers between pools ----------------------------------------------- If no auto-migration, if you can satisfy a transfer request to Sapling from your Sapling funds, do so. The user's consent is needed to reveal amounts. Therefore, there should be per-transaction opt-in for any amount-revealing transfer. * there may be a compatibility issue for amount-revealing cross-pool txns that were previously allowed without opt-in Don't automatically combine funds across pools to satisfy a transfer (since that could reveal the total funds in some pool). In order to maintain the integrity of IVK guarantees, wallets should not generate unified addresses that contain internal receivers, nor expose internal receivers (such as those used for auto-shielding and change outputs) in any way. Open questions: * should there be an auto-migration option from Sapling to Orchard? # str4d notes If we want to have both automatic and opportunistic shielding, and keep the two indistinguishable, then we can't auto-shield when the transparent balance reaches some threshold (otherwise opportunistic would either never be used, or would be identifiable when it uses the balance below the threshold). Instead, a proposition: we define a distribution of "time since last payment to the address" from which we sample the time at which the auto-shielding transaction will be created. This distribution is weighted by the balance in the address, so as more funds accrue, the auto-shielding transaction is more likely to be created. - It ensures that all funds will eventually be auto-shielded, while preventing fee-dusting attacks (where dust is sent in order to repeatedly consume fees from the wallet), as the auto-shielding transaction is not directly triggered by payment receipt. - If the user makes a shielding transaction in the meantime, we opportunistically shield, without it being clearly not an auto-shielding transaction. - If a wallet is offline for a long time, then it would likely auto-shield as soon as it finishes syncing. This maybe isn't enough to reveal that the wallet came online, except that it _might_ result in auto-shielding transactions for multiple transparent addresses being created at the same time. So we might want to special-case this? Properties we want from auto-shielding: - Auto-shielding transactions MUST NOT shield from multiple transparent receivers in the same transaction. - Doing so would trivially link diversified UAs containing transparent receivers. Properties we want from auto-migration: - Receipt of a shielded payment MUST NOT trigger any on-chain behaviour (as that reveals transaction linkability). Both auto-shielding and auto-migration are time-triggered actions, not receipt-triggered actions. An auto-shielding or auto-migration transaction MUST NOT be created as a direct result of a payment being received. Both of these are opportunistic: if the user's wallet is making a transaction in which one of these actions would occur anyway, then the wallet takes the opportunity to migrate as much as it would do if it were generating an autoshielding transaction. This both saves on a transaction, and removes the need for any kind of transparent change address within UAs. TODO: what pool should change go to? * Proposal: the most recent pool already involved in the transaction. Wallet Recovery --------------- In the case where we are recovering a wallet from a backed-up mnemonic phrase, and not from a wallet.dat, we don't have enough information to figure out what receiver types the user originally used when deriving each UA under an account. We have a similar issue if someone exports a UFVK, derives an address from it, and has a payment sent to the address: zcashd will detect the payment, but has no way to figure out what address it should display in the UI. A wallet could store this information in the memo field of change outputs, but that adds a bunch of coordination to the problem, and assumes ongoing on-chain state storage. - If the receiver matches an address that the wallet knows was derived via z_getaddressforaccount, show that UA as expected (matching the receiver types the user selected). - If the receiver matches a UFVK in the wallet, and we are looking it up because we detected a received note in some block, show the UA with the default receiver types that zcashd was using as of that block height (ideally the earliest block height we detect), and cache this for future usage. - For zcashd's current policy of "best and second-best shielded pools, plus transparent pool", that would mean Orchard, Sapling, and transparent for current block heights. - For each release of a wallet, the wallet should specify a set of receiver types and an associated range of block heights during which the wallet will, by default, generate unified addresses using that set of receiver types. - For zcashd we know how the policy evolves because each zcashd release has an approximate relase height and EoS height that defines the window. - Subsequent releases of a wallet should not retroactively change their policies for previously defined block height ranges. - If the receiver type for a note received at a given type is not a member of the set of receiver types expected for the range of block heights, the policy corresponding to the nearest block height range that includes that receiver type should be used. - If the receiver matches a UFVK in the wallet, and we have no information about when this receiver may have been first used, show the UA corresponding to the most recent receiver types policy that includes the receiver's type. - As part of this, we're also going to change the "Sapling receiver to UfvkId" logic to trial-decrypt after trying internal map, so that we will detect all receivers linked to UFVKs in the wallet, not just receivers in addresses generated via z_getaddressforaccount. The internal map lookup is then just an optimisation (and a future refactor to have Orchard do the same is possible, but for now we will only trial-decrypt so we don't need to refactor to access the Rust wallet). References ========== .. [#RFC2119] `RFC 2119: Key words for use in RFCs to Indicate Requirement Levels `_