zebra/book/src/dev/rfcs/0009-zebra-client.md

357 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- Feature Name: (`zebra-client`)
- Start Date: (2020-10-14)
- Design PR: [ZcashFoundation/zebra#0000](https://github.com/ZcashFoundation/zebra/pull/1163)
- Zebra Issue: [ZcashFoundation/zebra#0000](https://github.com/ZcashFoundation/zebra/issues/0000)
# Summary
[summary]: #summary
The `zebra-client` crate handles *client functionality*. Client functionality
is defined as all functionality related to a particular user's private data,
in contrast to the other full node functionality which handles public chain
state. This includes:
- note and key management;
- transaction generation;
- a client component for `zebrad` that handles block chain scanning, with
appropriate side-channel protections;
- an RPC endpoint for `zebrad` that allows access to the client component;
- Rust library code that implements basic wallet functionality;
- a `zebra-cli` binary that wraps the wallet library and RPC queries in a command-line interface.
Client functionality is restricted to transparent and Sapling shielded
transactions; Sprout shielded transactions are not supported. (Users should
migrate to Sapling).
# Motivation
[motivation]: #motivation
We want to allow users to efficiently and securely send and receive funds via
Zebra. One challenge unique to Zcash is block chain scanning: because
shielded transactions reveal no metadata about the sender or receiver, users
must scan the block chain for relevant transactions using *viewing keys*.
This means that unlike a transparent blockchain with public transactions, a
full node must have online access to viewing keys to scan the chain. This
creates the risk of a privacy leak, because the node should not reveal which
viewing keys it has access to.
Block chain scanning requires a mechanism that allows users to manage and
store key material. This mechanism should also provide basic wallet
functionality, so that users can send and receive funds without requiring
third-party software.
To protect user privacy, this and all secret-dependent functionality should
be strongly isolated from the rest of the node implementation. Care should be
taken to protect against side channels that could reveal information about
viewing keys. To make this isolation easier, all secret-dependent
functionality is provided only by the `zebra-client` crate.
# Definitions
[definitions]: #definitions
- **client functionality**: all functionality related to a particular user's
private data, in contrast to other full node functionality which handles
public chain state.
- **block chain scanning**: the process of scanning the block chain for
relevant transactions using a viewing key, as described in [§4.19][ps_scan]
of the protocol specification.
- **viewing key**: Sapling shielded addresses support *viewing keys*, which
represent the capability to decrypt transactions, as described in
[§3.1][ps_keys] and [§4.2.2][ps_sapk] of the protocol specification.
- **task**: In this document, *task* refers specifically to a [Tokio
task][tokio-task]. In brief, a task is a light weight, non-blocking unit of
execution (green thread), similar to a Goroutine or Erlang process. Tasks
execute independently and are scheduled co-operatively using explicit yield
points. Tasks are executed on the Tokio runtime, which can either be single-
or multi-threaded.
[ps_scan]: https://zips.z.cash/protocol/protocol.pdf#saplingscan
[ps_keys]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys
[ps_sapk]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
[tokio-task]: https://docs.rs/tokio/0.2.22/tokio/task/index.html
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
There are two main parts of this functionality. The first is a `Client`
component running as part of `zebrad`, and the second is a `zebra-cli`
command-line tool.
The `Client` component is responsible for blockchain scanning. It maintains
its own distinct `sled` database, which stores the viewing keys it uses to
scan as well as the results of scanning. When a new block is added to the
chain state, the `Client` component is notified asynchronously using a
channel. For each Sapling shielded transaction in the block, the component
attempts to perform trial decryption of that transaction's notes using each
registered viewing key, as described in [§4.19][ps_scan]. If successful,
decrypted notes are saved to the database.
The [`PING`/`REJECT`][pingreject] attack demonstrates the importance of
decoupling execution of normal node operations from secret-dependent
operations. Zebra's network stack already makes it immune to those particular
attacks, because each peer connection is executed in a different task.
However, to eliminate this entire class of vulnerability, we execute the
`Client` component in its own task, decoupled from the rest of the node
functionality. In fact, each viewing key's scanning is performed
independently, as described in more detail below, with an analysis of
potential side-channels.
[pingreject]: https://eprint.iacr.org/2020/220.pdf
The second part is the `zebra-cli` command-line tool, which provides basic
wallet functionality. This tool manages spending keys and addresses, and
communicates with the `Client` component in `zebrad` to provide basic wallet
functionality. Specifically, `zebra-cli` uses a distinct RPC endpoint to load
viewing keys into `zebrad` and to query the results of block chain scanning.
`zebra-cli` can then use the results of those queries to generate transactions
and submit them to the network using `zebrad`.
This design upholds the principle of least authority by separating key
material required for spending funds from the key material required for block
chain scanning. This allows compartmentalization. For instance, a user could
in principle run `zebrad` on a cloud VPS with only their viewing keys and
store their spending keys on a laptop, or a user could run `zebrad` on a
local machine and store their spending keys in a hardware wallet. Both of
these use cases would require some additional tooling support, but are
possible with this design.
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
## State notifications
We want a way to subscribe to updates from the state system via a channel. For
the purposes of this RFC, these changes are in-flight, but in the future, these
could be used for a push-based RPC mechanism.
Subscribers can subscribe to all state change notifications as they come in.
Currently the `zebra_state::init()` method returns a `BoxService` that allows you to
make requests to the chain state. Instead, we would return a `(BoxService,
StateNotifications)` tuple, where `StateNotifications` is a new structure initially
defined as:
```
#[non_exhaustive]
pub struct StateNotifications {
pub new_blocks: tokio::sync::watch::Receiver<Arc<Block>>,
}
```
Instead of making repeated polling requests to a state service to look for any
new blocks, this channel will push new blocks to a consumer as they come in,
for the consumer to use or discard at their discretion. This will be used by
the client component described below. This will also be needed for gossiping
blocks to other peers, as they are validated.
## Online client component
This component maintains its own Sled tree. See RFC#0005 for more details on Sled.
We use the following Sled trees:
| Tree | Keys | Values |
|-----------------------|-----------------------|-------------------------------------|
| `viewing_keys` | `IncomingViewingKey` | `String` |
| `height_by_key` | `IncomingViewingKey` | `BE32(height)` |
| `received_set_by_key` | `IncomingViewingKey` | ? |
| `spend_set_by_key` | `IncomingViewingKey` | ? |
| `nullifier_map_by_key`| `IncomingViewingKey` | ? |
See https://zips.z.cash/protocol/protocol.pdf#saplingscan
Zcash structures are encoded using `ZcashSerialize`/`ZcashDeserialize`.
This component runs inside zebrad. After incoming viewing keys are registered,
it holds onto them in order to do blockchain scanning. The component keeps track
of where its scanned to (TODO: per key?). Runs in its own separate task, in
case it crashes, its not noticeable, and executes independently (but in the
same process) of the normal node operation.
In the case of the client component that needs to do blockchain scanning and
trial decryption, every valid block with non-coinbase transactions will need to
be checked and its transactions trial-decrypted with registered incoming viewing
keys to see if any notes have been received by the key's owner and if any notes
have already been spent elsewhere.
## RPC's
A specific set of _privileged_ RPC endpoints:
- Allows registering of incoming viewing keys with zebrad in order to do
blockchain scanning
- Allows querying of the results of that scanning, to get wallet balance, etc
- Not authenticated to start (see 'Future possibilities')
- Users can control access by controlling access to the privileged endpoint (ie
via a firewall)
Support for sending tx's via _non-privileged_ RPC endpoints, or via Stolon:
- sendTransaction: once you author a transcation you can gossip it via any
Zcash node, not just a specific instance of zebrad
## Wallet functionality
- Holds on to your spending keys so you can author transactions
- Uses RPC methods to query the online client component inside zebrad about
wallet balances
## CLI binary
- zebra-cli talks to the subcomponent running in zebrad
- (can use servo/bincode to communicate with zebrad)
- via the privileged (and possibly the unprivileged) RPC endpoints
- can use [cap-std](https://blog.sunfishcode.online/introducing-cap-std/)
to restrict filesystem and network access for zebra-client.
See https://github.com/ZcashFoundation/zebra/issues/2340
- can use the [tui crate](https://crates.io/crates/tui) to render a terminal UI
## Task isolation in Tokio
- TODO: fill in
- cooperative multitasking is fine, IF you cooperate
- lots of tasks
<!-- This is the technical portion of the RFC. Explain the design in sufficient detail that: -->
<!-- - Its interaction with other features is clear. -->
<!-- - It is reasonably clear how the feature would be implemented, tested, monitored, and maintained. -->
<!-- - Corner cases are dissected by example. -->
<!-- The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. -->
## Module Structure
<!-- Describe the crate and modules that will implement the feature.-->
zebra-client ( currently and empty stub) zebra-cli (does not exist yet)
zebra-rfc? (exists as an empty stub, we way have zebra-cli communicate with
zebra-client inside zebrad via an RPC method any/or a private IPC layer)
## Test Plan
<!-- Explain how the feature will be tested, including: -->
<!-- * tests for consensus-critical functionality -->
<!-- * existing test vectors, if available -->
<!-- * Zcash blockchain block test vectors (specify the network upgrade, feature, or block height and network) -->
<!-- * property testing or fuzzing -->
<!-- The tests should cover: -->
<!-- * positive cases: make sure the feature accepts valid inputs -->
<!-- * using block test vectors for each network upgrade provides some coverage of valid inputs -->
<!-- * negative cases: make sure the feature rejects invalid inputs -->
<!-- * make sure there is a test case for each error condition in the code -->
<!-- * if there are lots of potential errors, prioritise: -->
<!-- * consensus-critical errors -->
<!-- * security-critical errors, and -->
<!-- * likely errors -->
<!-- * edge cases: make sure that boundary conditions are correctly handled -->
# Drawbacks
[drawbacks]: #drawbacks
<!-- Why should we *not* do this?-->
Supporting a wallet assumes risk. Effort required to implement wallet functionality.
- need to responsibly handle secret key material;
- currently we only handle public data.
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
<!-- - What makes this design a good design? -->
<!-- - Is this design a good basis for later designs or implementations? -->
<!-- - What other designs have been considered and what is the rationale for not choosing them? -->
- why have a separate RPC endpoint?
- extra endpoints are cheap
- allows segmentation by capability
- alternative is error-prone after-the-fact ACLs like Tor control port filters
- What is the impact of not doing this?
- We can't send money with zebra alone.
- rely on third party wallet software to send funds with zebra
- we need to provide basic functionality within zebra's trust boundary, rather than forcing users to additionally trust 3p software
- there are great 3p wallets, we want to integrate with them, just don't want to rely on them
- What about the light client protocol?
- does not address this use case, has different trust model (private lookup, no scanning)
- we want our first client that interacts with zebrad to not have a long
startup time, which a lightclient implementation would require
- zebra-cli should be within the same trust and privacy boundary as the
zebrad node it is interacting with
- light client protocol as currently implemented requires stack assumptions
such as protobufs and a hardcoded lightserver to talk to
- What about having one database per key?
- easy to reliably delete or backup all data related to a single key
- might use slightly more space/CPU
- slightly harder to delete all the keys
# Unresolved questions
[unresolved-questions]: #unresolved-questions
<!-- - What parts of the design do you expect to resolve through the RFC process before this gets merged? -->
<!-- - What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -->
<!-- - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? -->
- wait to fill this in until doing the detailed writeup.
# Future possibilities
[future-possibilities]: #future-possibilities
- [BlazeSync algorithm](https://forum.zcashcommunity.com/t/zecwallets-blazesync-sync-entire-chain-in-60s/39447)
for fast syncing, like Zecwallet
- mandatory sweeps for legacy keys
- blazingly fast wallet startup, to match `zebrad`'s blazingly fast sync
- generate unified address from a new seed phrase (or one provided by the user)
- user can just backup seed phrase rather than a set of private keys
- handles arbitrary keys from `zcashd` and other wallets, even if they weren't generated from a seed phrase
- ~handles Sprout funds without `zebra-client` having to support Sprout balances~
- startup is incredibly fast
- sweep takes a few minutes to be confirmed
- scanning the entire chain could take hours
- if we know when the seed phrase was created, we can skip millions of blocks during scanning
- sweeps can also be initiated by the user for non-linkability / performance / refresh
- sweeps should handle the "block reward recipient" case where there are a lot of small outputs
- initial release could support mandatory sweeps, and future releases could support legacy keys
- split `Client` component into subprocess
- this helps somewhat but the benefit is reduced by our preexisting memory safety, thanks to Rust
- not meaningful without other isolation (need to restrict `zebrad` from accessing viewing keys on disk, etc)
- could use [cap-std](https://blog.sunfishcode.online/introducing-cap-std/)
to restrict filesystem and network access for zebra-client.
See https://github.com/ZcashFoundation/zebra/issues/2340
- instead of process isolation, maybe you actually want the Light Client
Protocol, or something similar?
- hardware wallet integration for `zebra-cli`
- having `zebra-cli` allows us to do this
- much higher security ROI than subprocess
- very cool future feature
- authenticate queries for a particular viewing key by proving knowledge of the
viewing key (requires crypto). this could allow public access to the client
endpoint
- Use Unified Addresses only, no legacy addrs.
<!-- Think about what the natural extension and evolution of your proposal would -->
<!-- be and how it would affect Zebra and Zcash as a whole. Try to use this -->
<!-- section as a tool to more fully consider all possible -->
<!-- interactions with the project and cryptocurrency ecosystem in your proposal. -->
<!-- Also consider how the this all fits into the roadmap for the project -->
<!-- and of the relevant sub-team. -->
<!-- This is also a good place to "dump ideas", if they are out of scope for the -->
<!-- RFC you are writing but otherwise related. -->
<!-- If you have tried and cannot think of any future possibilities, -->
<!-- you may simply state that you cannot think of anything. -->
<!-- Note that having something written down in the future-possibilities section -->
<!-- is not a reason to accept the current or a future RFC; such notes should be -->
<!-- in the section on motivation or rationale in this or subsequent RFCs. -->
<!-- The section merely provides additional information. -->