Summary: Learn the inner workings of the architecture and engineering decisions behind River Financial’s new Hardware Wallet Account.
A previous product announcement highlighted the new Hardware Wallet Account that reduces friction for clients to self-custody bitcoin. This post provides an in-depth technical background on how the product works and discusses the engineering decisions.
The Hardware Wallet Account is an extension of a hardware wallet device, and serves as a non-custodial interface. It makes withdrawing bitcoin to a wallet easy and reduces the potential for error. The Hardware Wallet Account addresses the following problems:
- Withdrawing to an offline hardware wallet would require several steps. A client needs to access their wallet device and generate an address for each withdrawal. After address generation, this address must be safely copied and pasted into an input form. A wallet device is not always readily nearby, therefore a simple withdrawal can span over many hours.
- Clients would lose the ability to use our Performance Tracking tool when they move coins into their own custody. The ability to use our tooling should not be dependent on our ownership of private keys.
- Bitcoin owned by offline hardware wallets is difficult to monitor and can be improved by relying on River Financial to watch for new transactions. It’s more convenient to view your entire up-to-date bitcoin balance on the River Financial dashboard.
Our solution to these frictions is the hardware wallet account, which allows a user to register their Ledger or Trezor device with River, sharing their xpub with our service, and use the following features:
- View the wallet balance and transaction history on River.
- Track investments using the performance dashboard.
- Generate addresses and receive bitcoin.
- Keep tax lot information when transferring bitcoin from River brokerage to the device.
Protected using cryptography, River cannot move the coins on the device and the client is fully empowered to take self-custody of their bitcoin.
The remainder of the blog describes the behind-the-scenes of each step in the Hardware Wallet Account, discusses limitations, and highlights a future technical roadmap.
The device registration involves the communication between River and the device, and is the natural first step in our product. During Hardware Wallet Account registration, River prompts the user to connect their wallet device and accept a series of API requests. River requests the absolute minimum amount of information from the wallet device in order to create a wallet descriptor. Safe communication happens between the client browser and River using a variety of transportation methods. Depending on the operating system and browser, the transport method can be U2F, WebUSB, or WebHID. A series of API calls are sent to the device using the transport method to request a public key at a particular derivation path and signature for that public key. Additionally, a public key might be requested at the root derivation to get the parent key fingerprint.
The public key requested corresponds to a wallet account belonging to the hardware wallet device.
Modern wallets are hierarchical deterministic wallets, meaning that there is a root key and child keys along the tree, which act as wallet accounts. A wallet device can have several wallet accounts. For example, a typical wallet device might have a “Segwit Account #1” which means this wallet account contains a tree of children keys at index 1.
The key is serialized into an extended public key (xpub). Defined in BIP32, an extended key encodes information to allow an application to derive a number of child keys for a hierarchical deterministic wallet. This key can only derive child public keys. By this design, River can only derive public keys descending down the BIP32 tree, and the keys belonging to the rest of the tree are not observable. As an immediate benefit, the privacy of bitcoin ownership in other accounts on the wallet is preserved for the client.
An xpub is not enough information to create a wallet, therefore we turn to creating a wallet descriptor. Output Script Descriptors, or descriptors, is a language for describing scripts that is authored by Pieter Wuille and used in Bitcoin Core. Descriptors are used to define wallets in our system so that we can have an expressive way to scope wallets using their keys and scripts. An xpub on its own does not let us know which script type and derivation paths to use. The remaining pieces of information to build our descriptor is a parent key fingerprint, derivation paths, and the script type we plan to use for Bitcoin addresses.
For more information on how River Financial uses descriptors, read our Bitcoin Optech Field Report.
A parent key fingerprint acts as an identifier for the entire key tree, and is a requirement for transaction signing later on. Two derivation paths are needed: an origin derivation path and the derivation path following the extended public key. The origin derivation path is the path along the key tree that results in the public key mapping to the wallet account. The ecosystem standards for the origin derivation path are listed in BIP44.
As an example, wallet operators agree to use m/84’/1’/0’ as the first account for a Testnet Pay-to-Witness-Public-Key-Hash (P2WPKH) Bitcoin address. The initial levels of the tree, called nodes, have purposeful meaning for a wallet operator. In our example, 84’ informs the wallet to create P2WPKH scripts using the public keys. The 1’ maps to the chain the wallet belongs to. In our case, this is testnet. The final 0’ is the account index to derive children public keys from.
The script type can vary for the particular wallet that is being registered. Today, we only support registered P2WPKH scripts for Ledger and nested-P2SH (P2SH-P2WPKH) scripts for Trezor. The descriptor is created and stored in our wallet service walletd.
In addition to an xpub, a signature for an arbitrary message is requested from the device corresponding to the public key at an address index from the xpub’s child BIP32 tree. The signature is ultimately used as a sanity check for our system and assures that the device can sign at a later time. Bitcoin uses Elliptic Curve Digital Signature Algorithm (ECDSA) to sign messages. As a verifier, we can verify that a device has access to a private key by asking it to sign a message. A useful feature of ECDSA signatures is public key recovery which allows us to reconstruct the public key for a given message and signature. As an added benefit, we can assert that the derived public key from using the descriptor matches the recovered public key.
After a descriptor is created and a signed message is verified, the client has successfully registered their device and walletd can start to service it.
Our descriptor provides the minimal information to create Bitcoin addresses, but the stateful information like client balances and transaction history are missing. A new watch-only wallet might already have transaction history and ownership of UTXOs, therefore rescanning the chain and UTXO set is necessary to rebuild history. At a high level, walletd creates candidate keypools containing scripts and sends they keypool to indexd which manages an internal index of the entire bitcoin blockchain. The result is a list of ordered transactions that contains scripts belonging to the keypool.
The candidate keypool is a list of scriptPubKeys belonging to wallet up to a variable index limit. Walletd starts a rescan by sending an initial limit of scripts. Indexd’s fast index of scriptPubKeys and outpoints allows us to quickly query for transaction history by scriptPubKey. If the gap limit between the last found scriptPubKey and the index limit is too close, walletd sends another request with a larger keypool.
Back to our BIP32 tree of public keys. We can create an appropriate bitcoin script using the descriptor. On the chain, this script is titled scriptPubKey and is often called “locking script.” The candidate keypool is created by making a list of many scripts by traversing by breadth or incrementing the address index in the BIP32 tree. We can find if a script has an Bitcoin output by checking a transaction’s output’s scriptPubKey and using pattern matching. If the script equals, then an address belonging to the wallet owns an output.
Indexd might return a the list of ordered transactions associated with the wallet’s keypool, or let walletd know that the wallet has no existing history on chain or in the mempool. Walletd assigns this watch-only wallet a birthday related to its earliest transaction’s block height and saves its last unused address index.
At this point, we have recovered the wallet and all of its stateful information including its transaction history and current UTXO ownership.
A successfully registered wallet device can start to generate Bitcoin receive addresses on River without the device being connected again. After a successful keypool is created, walletd maintains a list of scripts belonging to the wallet and can create new ones on-demand. A client can generate addresses for their hardware wallet device while never touching the device. This allows the clients to receive bitcoin directly to their cold storage.
The address index is a pointer to the last unused address index on the BIP32 tree. When a client requests a new address for their wallet, it is always new. We follow best practices by treating addresses as single use tokens. Once an address has been detected by walletd to own a new output, the address index is incremented. As a result, the address display on River is the same address a client might see on other wallet software like Ledger Live or Trezor Wallet.
In addition to this pointer, another keypool is created called the keypool gap which maintains a list of future scripts derived past the address index up to a limit called the look ahead window. If a client derives many addresses on another wallet and receives bitcoin to an address past the address index pointer, then walletd will still be notified using the look ahead window.
As mentioned earlier, only P2WPKH and P2SH-P2WPKH scripts are supported. An intentional decision was made to avoid supporting legacy scripts like P2PKH. First, River can benefit the Bitcoin ecosystem by encouraging clients to adopt SegWit and reduce transaction bloat on the chain. Second, the list of problems associated with transaction malleability can be avoided.
Walletd is notified for any new transactions belonging to the watch-only wallet. Using both the look ahead window and existing scripts, walletd can watch for utxo changes associated with relevant scriptPubKeys in the mempool or chain. The client is notified when a transaction occurs. Clients can keep their cold storage wallet device offline while maintaining excellent visibility of their bitcoin.
A client can transfer bitcoin from River to their wallet device without ever copying and pasting an address. On our transfer page, we pre-populate the last unused bitcoin address belonging to the recipient Hardware Wallet account. In addition to moving bitcoin easily, we also transfer tax lot information from a Brokerage account to Hardware Wallet account. You can view your tax lots as well as edit unknown tax lots on bitcoin that is owned by your wallet device. If it is ever time to sell Bitcoin, the Client can transfer Bitcoin back to River.com and the tax lot information will be transferred as well.
Laying the Foundation
The bitcoin infrastructure stack at River Financial has been built from the ground up with the mindset to have the best enterprise-grade bitcoin software. This product is the initial step toward a series of engineering efforts to blend traditional custodial finance and self-ownership. Moving forward, River Financial will continue to support more exciting non-custodial products for the long term Bitcoin investor including:
- Sending bitcoin from River with your hardware wallet device using the Partially Signed Bitcoin Transaction (PSBT) format as the foundation.
- Use River’s enterprise bitcoin software to take advantage of reliable coin selection, coin control, and competitive fee estimation.
- Maintain better visibility of your bitcoin wallet including awareness about UTXO consolidation, block confirmation estimates, and other advanced features.
- An option to enable sweeping transactions that serves as an additional safe backup for your bitcoin.
- Support more wallets including software and other hardware wallets.