Segregated Witness and Hardware Wallets
Everybody agrees that Segregated Witness is cool — if you need more information about it, other than knowing that it’s a clever encoding trick enabling future capacity increase on the Bitcoin network, we recommend reading the Segregated Witness Benefits description from Bitcoin Core or the article series on Bitcoin Magazine. This post focuses on the generic benefits of Segregated Witness for Hardware Wallets with specific technical details about our own implementation.
The Hardware Wallet job
Hardware wallets protect Bitcoin keys and make sure that you are paying the right address and amount. For that, they need to know some information about the current transaction : the amount put in, the destination address and destination amount.
Unfortunately, this amount is not present in the Previous Output information which qualifies each transaction input — the Hardware Wallet developer shall find a solution to solve that, otherwise an attacker could lie on the amount associated to the inputs, resulting in an inflated fee, as the fee is coded as the difference of the outputs paid and the inputs provided.
After validating the transaction, the Hardware Wallet signs it — with the current process this involves modifying a copy of the full transaction for each signed input. It leads to strong design choices as Hardware Wallets usually do not have enough RAM to hold an arbitrary transaction.
Associating a value to an input
We solve this problem by creating a proprietary Previous Output structure called a Trusted Input — the Hardware Wallet parses the full previous transaction, and returns a signed Previous Output with an associated value.
Even if this can be done in the background, it is not an optimal solution — the previous transaction still has to be fetched from the network then streamed to the Hardware Wallet. This can be quite painful if the previous transaction comes from a service that groups payments into large transactions such as a lottery or a mining pool reward (for example 3ed9dcca9…5d2c2471a).
The streaming speed is also quite slow on our implementation as we stick to smartcard standards — we use an ISO 7816–4 T=0 encoding on top of HID to be able to move easily to CCID for vanilla ISO smartcards or NFC, and our transaction parser has to work around the RAM limitation of Ledger Nano (around 2 Kb available).
The new signature algorithm enforced with Segregated Witness solves this issue in the cleanest way possible by including the Previous Output value in the signature directly — thus Hardware Wallets don’t have to process the previous transaction any longer, and the client doesn’t have to request the full previous transaction either, as any change of the associated value would invalidate the signature of this input.
Signing a transaction
Given the current process, signing a transaction is done as follows with our protocol, after having obtained the Trusted Input with GET TRUSTED INPUT :
- Prepare the copy of the transaction with the right input script prepared on the host computer
- Stream the transaction to the dongle with UNTRUSTED HASH TRANSACTION INPUT START
- Stream the output script with UNTRUSTED HASH TRANSACTION INPUT FINALIZE FULL
- Request the signature with UNTRUSTED HASH SIGN
- Repeat for each input
We can see that the process is quite complex — especially when collecting a large number of low value inputs. Of course if we collect large number of low value inputs coming from a large transaction it’s even worse due to the previous problem, and that’s what happens most of the time with those services.
Segregated Witness also helps for that — the signature mechanism complexity has been reviewed to avoid rehashing the full transaction for each input and help node validation when dealing with mega transactions, but we can reuse the same logic for every transaction, keep an image of the data to hash and skip most of the previous process.
The procedure is changed when signing a Segregated Witness input as follows :
- Stream the transaction up to its outputs to the dongle with UNTRUSTED HASH TRANSACTION INPUT START. We don’t need to use Trusted Inputs and can directly push the Previous Output and its associated amount.
- Stream the output script with UNTRUSTED HASH TRANSACTION INPUT FINALIZE FULL. At that point, the Hardware Wallet can cache the state of the hash for the full transaction.
- For each input, stream a pseudo transaction with a single input and no output with UNTRUSTED HASH TRANSACTION INPUT START then request the signature with UNTRUSTED HASH SIGN
Let’s see how much time we save on our implementation for a sample transaction with 4 inputs, each previous transaction being roughly 400 bytes long :
- The trusted input computation (4 times) : 330 ms * 4 times : 1.3 seconds
- The generic transaction parsing : 1 second * 3 times : 4 seconds
What’s left is only signature of the inputs:
- Inputs signatures (4 times) : 1 second * 4 times : 4 seconds
This is an economy of 5.4 seconds on a process of 9.4 seconds, which is about a 60% performance boost.
Two birds with one stone
Segregated Witness helps solving two major Hardware Wallets performance issues (trusted input computation and generic transaction parsing)— for us, it’s definitely shiny and chrome.
If you’re a developer, you can try it right away with the beta version of Ledger Wallet Nano, following our updated technical specification. It is also available in the recently released Trusted Execution Environment application for Android.