zips/zip-0315.rst

365 lines
13 KiB
ReStructuredText
Raw Normal View History

::
ZIP: 315
Title: Best Practices for Wallet Handling of Multiple Pools
Owners: Daira Emma Hopwood <daira@electriccoin.co>
Jack Grigg <jack@electriccoin.co>
Status: Proposed
Category: Wallet
Discussions-To: <https://github.com/zcash/zips/issues/447>
Pull-Request: <https://github.com/zcash/zips/issues/>
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.
TODO: add definitions of opportunistic shielding and migration.
TODO: "confirmed", "partially confirmed"
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.
As is true for consent in general, a user may give blanket consent to reveal a
particular kind of information, and must always be able to revoke consent to
reveal such information in the future. The specifications below describe some
situations in which blanket consent may be inappropriate.
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 notes
---------------------------
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 notes 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 notes:
* An untrusted note is a note received from a party that may try to double-spend.
* A trusted note is a note received from a party where the wallet trusts a
double-spend not to occur, e.g. notes created by the wallet's internal note
handling.
Wallets can then require that untrusted notes need more confirmations before
they become spendable than trusted notes. This provides an improved trade-off
between latency on the one hand, and reliability and safety on the other.
Wallets MAY enable users to mark specific external transactions as trusted,
allowing their received notes to be spent more quickly.
A wallet SHOULD have a policy that is clearly communicated to the user for
the number of confirmations needed to spend untrusted and trusted notes
respectively. The following confirmation policy is RECOMMENDED:
* 10 confirmations, for untrusted notes;
* 3 confirmations, for trusted notes.
A note is *confirmed spendable* if and only if the wallet has its spending
key, and:
* it is a trusted note that has at least the required confirmations for trusted
notes; or
* it is an untrusted note that has at least the required confirmations for
untrusted notes.
A note is *unconfirmed spendable* if and only if the wallet has its spending
key, and it is not confirmed spendable.
A note is *watch-only* if and only if the wallet has its full viewing key
but not its spending key.
Reporting of balances
---------------------
Wallets SHOULD report:
* Spendable balance.
* Pending funds, *or* total (spendable + pending) balance.
These are calculated as follows:
* The spendable balance is the sum of values of confirmed spendable notes.
* The pending ...
Incoming transactions (each with # confirmations, amount)
Sent transactions (each with # confirmations, how long until expiry, amount)
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 <https://www.rfc-editor.org/rfc/rfc2119.html>`_