This simple attack works only if we are re-using the same addresses. Nowadays we use HD wallets and when the transaction gets to the blockchain the spending address is already empty and the attacker gets a private key of an empty address. What we want instead is to get the master private key. The master private key is 64 bytes long and it is not directly involved in the signing equations. We need to find another way to leak it via nonces.

We are going to do the following: for every outgoing transaction we choose a nonce k such that the number r (x-coordinate of the point R=k×G) starts with an index i followed by the corresponding byte of the master private key mpk[i]. Then the r part of every signature will look like 01mpk[1]<some random crap>, 02mpk[2]<other random crap> and so on. To find k giving us the right r we need to try a few times. On every try we increase k by 1 and add G to the corresponding point R. As addition is much faster than multiplication we can find a correct nonce pretty quickly — the user may not even notice. And roughly after ~64 transactions, we will be able to reconstruct the full master private key. To add some privacy for the attacker we can find nonces that start not with i mkp[i] but with a XOR of this with the attaker’s key: i mpk[i] ⊕ attacker_key. Then only the attacker can reconstruct the key and the signatures don’t look suspicious.

Finding all transactions corresponding to the same wallet is not very hard — normally all transactions from the same HD wallet can be linked to each other, especially when we know what to expect in the first bytes of the signature.

To demo this attack I created a set of bitcoin transactions on the testnet starting from this to this. I used 0x00 as an attacker’s key so anyone can see the bytes of the master private key in the nonces of the signatures:

tx 0: r = 0057360015b25dc6ec...

tx 1: r = 016d24c28dff49f70f...

...

tx 63: r = 3f94476a5630120121...

And we can easily reconstruct the master private key of the attacked wallet — 576d...94 . Full code is also on GitHub.

Now the question is, can we fix it somehow? There are two ways. Both have certain pros and cons. The core problem in the current protocol is that we allow the hardware wallet to choose a value that will be directly encoded in the transaction. We need to take this freedom away either by forcing the hardware wallet to use a certain algorithm or by randomizing its choice using additional offset.

Fix 1. Commitments.

First, let’s talk about randomization. We allow the hardware wallet to choose a nonce however it wants, but then we fix this choice by asking for a commitment and provide an additional random number for an offset. Hardware wallet then has to add this number to its nonce and use their sum in the signature scheme. In this situation, if one of the devices is behaving properly, the resulting nonce is random and it can’t contain any additional information.

To be more precise we require the following procedure:

the hardware wallet chooses a random number k1 and commits to it by disclosing a corresponding point R1 = k1×G

and commits to it by disclosing a corresponding point the computer sends unsigned transaction data and another random number k2 to the hardware wallet

to the hardware wallet the hardware wallet signs the transaction using the nonce k=k1+k2

the computer verifies that the signature and the transaction are valid and that r part of the signature is an x-coordinate of the point R=k×G=R1+k2×G, where R1 is a point the hardware wallet committed to in the beginning.

This way our computer checks that the hardware wallet used the nonce it committed to and added an offset that we provided. There are two drawbacks in this scheme:

the protocol requires several communication rounds, so with an air-gapped hardware wallet, we will need to move between the computer and the hardware wallet twice. Or we take two SD cards (one for the commitment and another one for the second random number and signed transaction).

the hardware wallet can’t use deterministic k anymore, it has to use truly random numbers from hardware RNG. And usually RNG = problems. The reason to use RNG is that if the computer will ask the wallet to sign the same transaction twice and provide two different numbers k2, k2', usage of the deterministic k1 will immediately reveal the secret key.

In total, this protocol is very easy to implement, but it is less convenient and may require a good source of randomness on the hardware wallet.

Update: We can still use deterministic k generation if the computer commits to its k2 and the hardware wallet uses this commitment to derive its k1. The whole communication process will look like this: • the computer chooses some value k2. Then it sends to the hardware wallet an unsigned transaction together with the commitment c=sha256(k2). • the hardware wallet deterministically calculates a nonce k1 from the transaction, the private key and the computer’s commitment c. Then the hardware wallet commits to this nonce by revealing R1=k1×G to the computer. • the computer sends its nonce k2 to the hardware wallet. • the hardware wallet checks that the nonce k2 hashes to the value c and signs the transaction using the nonce k=k1+k2. • the computer verifies that the signature and the transaction are valid and that r part of the signature is an x-coordinate of the point R=k×G=R1+k2×G. • now it’s safe to broadcast the transaction Thanks to @n1ckler for bringing this up. There is also a pull request to bitcoin core implementing this feature. And using this protocol with an airgapped wallet is not that painful — we can use two SD cards to sign the transaction. The first one will contain an unsigned transaction, a commitment c=sha256(k2) from the computer and later a commitment R1 from the hardware wallet. The second one will contain the nonce k2 and later a signed transaction from the hardware wallet.

Fix 2. Zero-knowledge proofs

Another option is to force the hardware wallet to use a particular algorithm to generate the nonce and to require a zero-knowledge proof of that. The current standard (RFC6979) uses SHA256 to derive a deterministic nonce from the message and the private key, but the corresponding zero-knowledge proof is extremely hard to calculate. Especially for a hardware wallet.

If you don’t know how zero-knowledge proofs work there is a very nice post by Vitalik Buterin on that (also check the references). Without going into details, zero-knowledge proofs are pretty tolerant to linear operations but blow up in size and complexity as soon as you add multiplications and other non-linear operations. Unfortunately, common hashing algorithms are very non-linear. Roughly speaking, calculating a ZK proof of SHA256 will be as difficult as calculating 10000 signatures. For a hardware wallet, it could take several minutes to generate a proof. Not very usable.

Fortunately, there are other hashing algorithms that are more ZK-friendly. In particular, MiMC hashing algorithm was specifically designed to be used with ZK proofs. We can tailor the deterministic nonce generation algorithm to use MiMC instead of SHA256. With MiMC the hardware wallet will be able to generate a proof in 20 seconds instead of several minutes. Then we can require the hardware wallet to include a ZK prove that this particular deterministic algorithm was used to generate a nonce for every signature. And therefore we can be sure that no data leak is possible. Hardware wallet doesn’t have any choice now. Everything is deterministic and provable.

There are two minor problems with this protocol:

MiMC is a pretty new hashing algorithm (2016), and we should make sure it is safe to use before deploying it in a real application. In particular, we need to be sure that it is not biased, uniformly distributed and blah blah blah.

ZK proofs are memory and computationally intensive. Especially when we talk about low performance embedded devices like 180MHz microcontrollers used in hardware wallets. And they are also theoretically complicated… They are pretty hard to understand and implement correctly. But still, doable.

Conclusion

It would be nice to see these or similar signing protocols realized in hardware and software wallets. I would definitely use it if I could. I believe we need to improve the security of our bitcoin storage setups and remove trust in manufacturers of our wallet software and firmware. We can’t read all the code we use, but we can verify that the protocol is used correctly.

I really like a phrase I’ve heard in quantum cryptography field: a good cryptographic setup can be verified and used for secure communication even if it was manufactured by an attacker. I would really like to get to the same level of confidence with our bitcoin setups.

And yeah, don’t forget to use your metal bucket and a foil cap!