JoinMarket-Org / joinmarket

CoinJoin implementation with incentive structure to convince people to take part
398 stars 119 forks source link

Hardware wallets for yield generator #537

Open chris-belcher opened 8 years ago

chris-belcher commented 8 years ago

I used to believe hardware wallets for yield generator were impossible for this reason: https://www.reddit.com/r/joinmarket/comments/3gl2jg/hardware_wallets_for_jm/cuewwqo?context=1

But after some chats with somebody they pointed out that this problem can be fixed by providing the input transaction hex as well to the hardware wallet which can check the amounts.

To help, we should edit the joinmarket code slightly to make it easier to integrate with a hardware wallet. Right now the AbstractWallet class requires a function called get_key_from_addr() but that could be replaced with one called sign_transaction() which would allow the hardware wallet to take an unsigned transaction, check it makes the balance of the wallet go up, and then sign it.

We also need a way for the hardware wallet to sign the encryption pubkey used in the protocol. Trouble is if it blindly signs some binary data it may end up signing the hash of a bitcoin transaction that steals money. So best to do would be to make the message be signed start with "joinmarket pubkey:". This would require a protocol update as in #171

chris-belcher commented 8 years ago

Instead of prepending with "joinmarket pubkey" we should prepend with "Bitcoin message" and it will then become the same thing as the "sign message with bitcoin address" feature from many bitcoin wallets.

chris-belcher commented 8 years ago

I've heard from a few people a major reason they don't run a yield generator with their held coins is they can't justify the hot wallet risk.

Here are some helpful links for implementing signatures that hardware wallets can blindly sign. You need to prepend "\x18Bitcoin Signed Message:\n" plus the message length. pybitcointools does it.

https://github.com/JoinMarket-Org/joinmarket/blob/66bd67a58a7fc242393a767edd478dd6258ec0ff/bitcoin/main.py#L394

As does Bitcoin Core

https://github.com/bitcoin/bitcoin/blob/042c323922fce00a1cd0d955a0c8b8bfa80e4045/src/main.cpp#L120

https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/rpc/misc.cpp#L352

https://github.com/bitcoin/bitcoin/blob/304eff3c614aa18f4d5d5d367024b0dbbb4dce4e/src/wallet/rpcwallet.cpp#L479

chris-belcher commented 7 years ago

Now that #171 has been implemented and that issue closed, hardware wallet creators could in theory start doing this.

instagibbs commented 7 years ago

One issue that arises when considering using a hardware wallet in any coinjoin scenario(not just yield) is that for it to sign a transaction with no user interaction, it must be certain it is not signing money away.

Modern HW wallets all take in the previous transactions as "trusted inputs"(Ledger terminology) to make sure the total value they are consuming is what it thinks it is. At least Ledger and Trezor do this.

The real issue here is that an attacker who controls input to your HW wallet could hand it inputs it claims isn't yours, but actually is. To modify the example from https://www.reddit.com/r/joinmarket/comments/3gl2jg/hardware_wallets_for_jm/cuewwqo/?context=1

Inputs: your input (2btc) [your input] your input (2btc) [hacker's input] hacker's input (1btc) [hacker's input] Outputs: your coinjoin address (2.5 btc) hacker's coinjoin address (1.5 btc)

So in this scenario, the HW wallet will simply sign, thinking "sweet I'm up .5 BTC!", then later sign the reverse transaction with the 2nd input revealed as yours instead of the first when presented by the attacker. Then you're really losing 1.5 BTC.

Therefore a protocol is required such that all inputs can come with a proof that it owns that input(or proof it doesn't).

Protocol sketch: 0) Each HW wallet has a static hidden value it never publicly reveals x for lifetime of device Then for each input you own: 1) Get address for that input A 2) Sign the message H(A || x) where H is some cryptographic hash. 3) Pass that signature and hash along with the "trusted inputs".

During a proposed join when HW wallet is presented with a list of "trusted inputs", for each input: 1) Check the signature proving ownership of address. If invalid, the join is invalid. 2) Now use local x to attempt to recreate the signed message hash H(A || x). If this matches, then it is your input. If it doesn't match, it cannot be your input.

kristapsk commented 5 years ago

Looks like a Coinkite has support for this on a roadmap for Coldcard wallet.

https://twitter.com/nvk/status/1058184253719044096 https://twitter.com/nvk/status/1058180870266281985