Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Ledger Integration #3081

Open
lukechampine opened this issue Jun 5, 2018 · 9 comments
Open

Ledger Integration #3081

lukechampine opened this issue Jun 5, 2018 · 9 comments

Comments

@lukechampine
Copy link
Member

We are evaluating writing a Sia app for the Ledger Nano S. Nothing is promised at this point. I'm creating this issue so that we can discuss design and track progress.

Plan

The current plan is to write a barebones Ledger app from scratch using the Nano S SDK that will support generating addresses and signing transactions. This app will talk to a client program on the user's computer -- preferably siac, but possibly a separate standalone program.

Ledger provides Bitcoin equivalents for these programs (blue-btc-app for the device itself and ledger-wallet-chrome for the client program), and as far as I can tell, pretty much every altcoin Ledger app is a fork of these. It's feasible that we could do the same for Sia: just replace the Bitcoin-specific code with Sia-specific code. ("Just" is a bit misleading; we don't really know how difficult this would be in practice.) However, we are uncomfortable with both the size of these programs and the dependency on Chrome. The larger a program is, the more difficult it is to audit. My hope is that we can produce a Ledger app that is minimal, easy to understand, and easy to audit. I unfortunately cannot ascribe these qualities to any other Ledger app I've looked at.

UX

The UX of our Ledger app will not deviate substantially from blue-app-btc. The flow for generating an address looks like this:

  1. User plugs Nano S into their computer and initializes it, generating a seed.
  2. User installs and opens Sia Ledger app.
  3. User runs siac wallet ledger address on their computer.
  4. Nano S displays address; user confirms, and the address is recorded in the user's siad wallet.

The flow for signing transactions is more complicated. For now, it will not be very user friendly.

  1. User uses the /wallet/unspent endpoint (added by Add support for offline transaction signing #2907) to get a list of outputs to spend.
  2. User constructs an unsigned transaction using the outputs.
  3. User runs siac wallet ledger sign [txn] [inputs to sign]
  4. Nano S displays each input being signed and the address + amount of each output.
  5. User confirms, and siac displays the signed transaction.
  6. User broadcasts signed transaction.

Implementation

One interesting thing I discovered is that the Nano S can only generate HD keys. There isn't a secure way to generate standard Sia keys using the Nano S's seed, because access to the seed is mediated by a few specialized functions.
This isn't a big deal; in practice, we'll do what other coins do: pick a Sia-specific derivation constant, and use the Sia "index" as the last part of the derivation path. This will all be invisible to the user. One potential pitfall is that the user may assume that they can import their Nano S seed into a standard siad wallet -- this is not at all the case.

#2907 suggests this interface for a hardware wallet:

type HardwareWallet interface {
    DeriveKey(index uint64) (pubkey [32]byte)
    SignHash(hash [32]byte, index uint64) (signature [64]byte)
}

And indeed, this is sufficient to implement key derivation and transaction signing. In this model, the Ledger app does the bare minimum, making its code maximally simple to understand and verify. However, this approach is fundamentally flawed, because it blindly signs whatever hashes the client program provides without user validation. Even if the user did want to validate the hashes, how would they? They would need to compare each hash to a "trusted" hash, and unless you can do blake2b in your head, that means trusting another computer. Remember, the entire reason to use a hardware wallet is because you don't trust the software running on your PC. We must assume that the user may be running a malicious version of siac. Which means that we must design the Ledger app such that even if it is talking to a malicious computer, the computer cannot trick the Ledger (or the user) into losing coins. At worst, the computer may be able to trick the user into thinking that they sent coins, when in reality the transaction was invalid and silently dropped.

So if users can't realistically validate hashes, what can they validate? Anything that appears in an explorer is probably fair game, so that means addresses, output IDs, and amounts. So a more secure interface would be:

type HardwareWallet interface {
    DeriveKey(index uint64) (pubkey [32]byte)
    Sign(txn types.Transaction, sigIndex int, keyIndex uint64) (signature [64]byte)
}

Sign would basically do what addSignatures does: calculate txn.SigHash(sigIndex) and sign it with the key derived from keyIndex. txn will have to include a half-formed TransactionSignature at sigIndex; Sign would only fill in the actual TransactionSignature.Signature field. It would then display txn.TransactionSignatures[sigIndex].ParentID, which would be the OutputID of the output being spent. The user would be responsible for confirming that this is the output they intend to spend.

This takes care of the inputs, but the user will always want to verify the outputs of the transaction. The Nano S should display the address and amount of each transaction output. This prevents the client program from altering the outputs.

The downside of this approach is that it requires a lot more work on behalf of the Nano S. We would need to port much of the transaction serialization code to C. It's certainly doable, and maybe not even that hard, but it's annoying because it would be much easier to do client-side. It also requires some fancier UI code (to scroll through the inputs and outputs), and writing UI code is (at least for me) much trickier than writing transaction signing code.

Security

The client program can try to trick the Nano S (or the user) into doing something they didn't intend to do. For our purposes, this essentially can be reduced to "sending coins to any address not explicitly approved by the user," but it's also possible that the client program could exploit a flaw in the Sia Ledger app to cause it to malfunction. This is the motivation for keeping our app code as minimal as possible.

The client program has one primary means of attack: displaying one piece of data to the user while sending/receiving a different piece of data to/from the Nano S. We already saw how this attack can work if the Nano S only receives a hash. The client program can tell the user that a given transaction hash is x, when in reality the hash is y and x is the hash of a different transaction.

The new implementation should thwart any such attack, as long as the user performs validation correctly. Here I will enumerate the main attacks I can think of, and how they fail. Please suggest more attacks if you think of any.

  • The client program modifies the outputs of the transaction before sending them to the Nano S. The user will see that the outputs are not what they expected, and refuse to sign.
  • The client program modifies the outputs of the transaction after sending them to the Nano S. This causes the signatures added by the Nano S to become invalid.
  • The client program modifies the CoveredFields field of the TransactionSignature so that the signature only covers one input. This type of signature would not become invalid if other parts of the transaction were altered. The Sia Ledger app should either refuse to sign CoveredFields that do not set the WholeTransaction flag, or display an extra warning to the user that non-standard CoveredFields are being used.
  • The client program calls DeriveKey or Sign with invalid parameters, such as a malformed txn or an out-of-bounds sigIndex. The Sia Ledger app must be able to detect these and throw an error.

Status

I have implemented some basic routines for key derivation and signing. The next step is to start porting the transaction decoding code, so that the Nano S can parse transactions received from the client program and display their inputs/outputs to the user. No validation will be performed at this point. Once the Nano S can perform these actions in isolation, we can move on to writing client program code to pass data to and from the Nano S. For now, we will likely use Ledger's C or Python API to communicate with the Nano S, but later on we may want to port the API to Go so that we can integrate the client program code directly into siac. If this proves infeasible, the client program will have to remain a standalone program.

@rocknet
Copy link

rocknet commented Jun 5, 2018

Keep in mind that the Chrome apps are being deprecated and new standalone versions of the apps are coming. https://www.ledger.fr/2018/02/23/announcing-new-ledger-wallet-desktop-mobile-applications/

As much as I'd like to see Sia on the ledger, the flows described above would not entice me to use it. I think ease of use is critical for this feature. Ideally it should work like the existing wallet app, but saving that, an integration like myetherwallet or even the Neon wallet (standalone app, seemless integration) would suffice.

@lukechampine
Copy link
Member Author

Thanks, that is good to know.
Ideally we could work closely with the Ledger team to get Sia on their wallets, but based on their roadmap, that seems unlikely to happen in the near future -- there are too many other coins competing for their attention.
I will give more consideration to forking the existing Ledger apps instead of writing our own. Our main concern is ensuring the security of the code running on the device itself. As I mentioned above, the device has to assume that it may be talking to a malicious client program anyway, so the security of the client program is of lesser importance.

One aspect that I need to research more is the question of full nodes. The Ledger Bitcoin wallet connects to Ledger's servers, not your local bitcoind. Sia doesn't have "light wallets," so our Ledger app must be able to connect to a local siad. It looks like the Ledger Ethereum wallet can connect to a local geth, so this sort of thing is definitely possible, but it might be tricky.

My current feeling is that for now, we should focus on the device code, and try to make its API match the other Ledger apps (even if the implementation is different). That way, we don't close off the possibility of interfacing with a Ledger wallet app in the future.

@rocknet
Copy link

rocknet commented Jun 5, 2018

I wonder if this might be helpful too: https://github.com/CityOfZion/neon-wallet/blob/dev/app/ledger/neonLedger.js

NEON wallet app has a button to use the Ledger to open the wallet and sign. I guess Ark works with way also. Sorry, I don't know how applicable this could be to SIA-UI. I've used NEON and MyEtherWallet with Ledger outside of the Bitcoin wallet app. I've not tried to use a local geth before.

@lukechampine
Copy link
Member Author

Progress update:

I have a Ledger Nano S that can generate Sia addresses. Keys are derived using the Ledger API's HD key derivation function. The resulting public key is then hashed as part of the UnlockConditions Merkle tree to produce an UnlockHash, and a final checksum is tacked on to complete the address. The address is displayed on the device so the user can confirm that it matches the address sent to the computer.

In a way, this is half of the equation: now I "just" need to add transaction signing, and the Ledger code will be more or less complete. In reality, the signing code is going to be much harder than the address generation code. The encoding/hashing/signing aspect will be tricky, but it's the UI that I'm worried about; so far I haven't implemented any non-trivial UI behavior, and the API is pretty scary. So don't hold your breath. :P

The Ledger SDK remains woefully undocumented, but I've been able to make good progress by borrowing heavily from the blue-app-eth code, which is surprisingly readable. I'm adding comments as I go, so hopefully when this is finished it will serve as a good base for someone looking to write their own Ledger app.

@DavidVorick
Copy link
Member

@lukechampine I think the industry would also benefit from you taking notes about all the places the API could be better, and all the things you find along the way that work well and don't work well.

It would be a good feature on the sia blog too.

@lukechampine
Copy link
Member Author

The single most helpful thing they could do is provide a way to test apps without having to load them onto an actual Ledger device. Even if everything was poorly-documented, with a fast enough turn-around, I could eventually figure it out. But right now, every time I want to test a change, I have to build the app, uninstall the old app, re-enter my PIN (ugh), wait for the new app to transfer, and then open it. As you might imagine, this can make it incredibly frustrating to track down a bug.

Aside from that, the main issue is a lack of documentation. Most of the Ledger API functions have passable docstrings, but they aren't very helpful without context and examples. Reading the code of other Ledger apps is much more fruitful than trying to figure something out just from the docs.

@lukechampine
Copy link
Member Author

It occurs to me that most Ledger apps are inadequate in terms of protecting you from a malicious computer.

When generating an address, the Ledger BTC and ETH apps will display the address on the device so that the user can confirm that the computer is not modifying it. But this is not enough! For one, if only the address is displayed, not the public key, then you can't use the key for anything other than receiving money at that address. Meaning you can't use the key for other purposes, e.g. a multi-sig address. But more dangerously, the ETH app (at least the python testing script) will in fact display the public key on the computer -- but not the device. For obvious reasons, this key cannot be trusted!

Even if the pubkey were displayed, there's another crucial piece of information that's missing: the HD key derivation path! This means the user is trusting the computer to tell the truth about which path it passed to the Ledger. It would be trivial for the computer to say "yep, this is the address for path m/44'/60'/0'/0," when it actually sent m/44'/60'/0'/100000 to the Ledger. To be clear, this attack does not allow the computer to steal your money; no matter what path is used, the address always "belongs" to your seed. But it can cause you to lose money, by sending it to an address whose path you do not know. If you don't know which path was used to derive a key, your only recourse is to search the entire path space (unless there's some BIP32 magic I don't know about that lets you figure out the path from the address). Worse, you have to perform this brute-force search on the Ledger itself, since only it knows the HD master seed. Needless to say, this would be prohibitively slow.

So my thinking is that the Sia Ledger app should display the path alongside the address. As for the public key, maybe we'll have a single screen for the path+pubkey+address, or maybe we'll have two separate screens: path+pubkey and path+address.

@rudi-cilibrasi
Copy link

To avoid the PIN entry you can install a custom certificate on the Nano S. Then install apps you sign. Still have to put on the real device so it is uncomfortable but saves a few presses for the PIN.

@lukechampine
Copy link
Member Author

ah, it hadn't occurred to me that signed apps don't need PIN entry -- thanks!

Progress update:

I've added the ability to sign arbitrary hashes. This means that the Sia Ledger app is now minimally useful! You can generate addresses, and as long as you can calculate transaction hashes in a trusted way, you can sign transactions! Of course, if you're paranoid about this stuff (and if you're using a Ledger, hopefully you are), you don't want to rely on some other computer to calculate the transaction hash for you. Even if you use an airgapped computer, there's some risk involved. So we really want to be calculating the hash on the Ledger itself. That's the next step. Still, I think the final version of the app will retain the ability to sign arbitrary hashes, since it's useful to have that sort of power as long as you understand the risks.

As part of the work required to implement hash signing, I've had to dive more deeply into how the Ledger UI works, and I feel like I have a better understanding of it now (although it's still mostly magic). Would you believe that the types and functions pertaining to the UI are almost completely undocumented? Fun stuff. But I think at this point it shouldn't be too hard for me to add an extra screen to the address generation and hash signing that displays the HD key derivation path. I really can't believe that other Ledger apps don't do this -- it's such an egregious omission that I feel like I must be missing something.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants