ElementsProject / elements

Open Source implementation of advanced blockchain features extending the Bitcoin protocol
MIT License
1.07k stars 381 forks source link

Pick a blinding key/blinding factor standard compatible with hww++ #448

Open instagibbs opened 6 years ago

instagibbs commented 6 years ago

Currently we kind of YOLO how these keys are derived, in that a backed up wallet.dat will properly restore funds, but these schemes are not cross-compatible with devices such as hardware wallets, and wallets that may not allow raw privkey export.

So I think for blinding derivation stuff we basically have:

1) signing keys on some path, hardened or unhardened paths from some hardened parent 2) master blinding key, which is actually Hash(some_pubkey_in_odd_hardened_path). This allows export of master blinding privkey for auditing purposes even for hardware wallets(xpub and xprv together to track funds in a non-custodial manner). This does mean an unlocked hww will cough up blinding pubkeys on a malicious host without intervention. 3) asset/value blinding factors: Some odd derivation path with hardened part, either take a single subkey and HMAC it with txid:nOut, or chunk up the txid and use it as a few normal derivation indices.

jgriffiths commented 6 years ago

Quick thoughts:

instagibbs commented 6 years ago

Using bytes to make a long derivation path is non ideal as its slow to derive, if we do this then we should use 31 bits per path element to keep the depth low. Ledger had a depth limit of 10 path elements last time I checked.

Yes there is the 10 depth requirement. We also need to avoid intentional txid prefix collisions by attackers, so likely not a good idea to truncate.

dgpv commented 5 years ago

master blinding key, which is actually Hash(some_pubkey_in_odd_hardened_path). This allows export of master blinding privkey for auditing purposes even for hardware wallets(xpub and xprv together to track funds in a non-custodial manner)

But right now the addresses are derived with hardened derivation (the path I see in dumped wallet is like m/0'/0'/5'), so right now you cannot give out xpub for watch-only wallet, as far as I understand.

Hash(some_pubkey_in_odd_hardened_path) this looks like just another form of key derivation. Why use it instead of just standard bip32 derivation with unique path that will never be reused ? (edit: To guarantee non-reuse by employing the different derivation method ?)

dgpv commented 5 years ago

An argument against using Hash(some_pubkey_in_odd_hardenedpath) - if there's a clash in derivation paths, and somehow for example 0x80'BLN' (0x80424c4e) derivation index gets reused, the pubkey is much likely to be revealed than privkey. If hash is used on top of bip32 derivation to get blinding key, it should be Hash(someprivkey_in_odd_hardened_path), in my opinion, and not pubkey.

dgpv commented 5 years ago

asset/value blinding factors: ... HMAC it with txid:nOut, or chunk up the txid and use it as a few normal derivation indices

Chunk up and use as indexes would be just 10 HMACs in succession with 31-bit chunks of the same data mixed in - as far as I understand, if underlying hash function is not broken, there's no advantage against one HMAC across all the data.

instagibbs commented 5 years ago

But right now the addresses are derived with hardened derivation (the path I see in dumped wallet is like m/0'/0'/5'), so right now you cannot give out xpub for watch-only wallet, as far as I understand.

This issue isn't relating just to Elements node software, but also to any other possible implementation of blinding derivation.

Hash(some_pubkey_in_odd_hardened_path) this looks like just another form of key derivation. Why use it instead of just standard bip32 derivation with unique path that will never be reused ? (edit: To guarantee non-reuse by employing the different derivation method ?)

The device may not give up the privkey at all, like a Ledger or other hww.

real-or-random commented 5 years ago

Here are some thoughts about key derivation, with future compatibility to Green Address and Hardware wallets in mind.

What we're currently doing is as follows:

Incoming transactions:

Outgoing transactions:

The problem here for outgoing transactions is that after a backup has been restored, we can only restore the outgoing transactions that happened before the backup.

We should instead do the following for outgoing transactions:

This means that after a backup has been restored, we can always recover everything from the chain. In particular, we can unblind past outgoing transactions in essentially the same way as we unblind incoming transactions, namely as we were the receiver but with the "other" half of the ECDH.

For incoming transaction, it somehow depends on what we want:

instagibbs commented 5 years ago

How is blinding_derivation_key computed?

A simple(currently unused) hardened derivation path should suffice for deriving this master. Something like https://github.com/ElementsProject/elements/pull/232 but maybe less randomly chosen.

real-or-random commented 5 years ago

That sounds reasonable too, assuming HD wallets.

Sosthene00 commented 4 years ago

Here are some thoughts about key derivation, with future compatibility to Green Address and Hardware wallets in mind.

What we're currently doing is as follows:

Incoming transactions:

* `recipient_ecdh_secret = HMAC-SHA256(key=blinding_derivation_key, msg=scriptPubkey_output)`,
  see https://github.com/ElementsProject/elements/blob/59def74d27e31e623957d234a586f5c1e77105bc/src/wallet/wallet.cpp#L5378

  Here `blinding_derivation_key` is a master key for the entire wallet, see https://github.com/ElementsProject/elements/blob/f08447909101bfbbcaf89e382f55c87b2086198a/src/wallet/walletdb.cpp#L584

  and there is the possibility to skip the derivation for imported blinding keys.

* (Issuance transactions are similar to incoming transactions. Instead of using it to perform a ECDH key exchange, we use the `recipient_ecdh_secret` directly. But in the end that does not really make a difference.)

Outgoing transactions:

* `blinding_factors = rand()` (and the results are stored in the wallet, I think), see https://github.com/ElementsProject/elements/blob/28ae91d7de461843d386969626af847a479d3e91/src/blind.cpp#L452

* `sender_ecdh_secret = rand()`, see https://github.com/ElementsProject/elements/blob/28ae91d7de461843d386969626af847a479d3e91/src/blind.cpp#L160

The problem here for outgoing transactions is that after a backup has been restored, we can only restore the outgoing transactions that happened before the backup.

We should instead do the following for outgoing transactions:

* `blinding_factors = random()`

* `sender_ecdh = HMAC-SHA256(key=blinding_derivation_key, msg=amount_commitment)`

This means that after a backup has been restored, we can always recover everything from the chain. In particular, we can unblind past outgoing transactions in essentially the same way as we unblind incoming transactions, namely as we were the receiver but with the "other" half of the ECDH.

For incoming transaction, it somehow depends on what we want:

* For non-HD wallets, what we do makes sense. You could give the `blinding_derivation_key` to other people and they could see your transactions.

* For HD wallets, there are (at least) two possible permission models. At the moment, we have the `blinding_derivation_key` that can be used to unblind everything. That's simple and works. An alternative model is to derive the blinding keys from the individual HD keys, then you don't only split the permission to send coins but in the same way you split the permission to unblind incoming transactions. This is more complex and it's unclear what to do for outgoing transaction: What if you have a transaction that spends from different HD-derivations/different permissions levels. Who should be able to unblind that outgoing transaction? I think we should keep it as it is for the moment (with the simple model) but at least document these subtle things for the user.

* When it comes to hardware wallets, the plan for GreenAddress is to derive the blinding information even for incoming transactions on the device, and then you can optionally give it to the host. So I think when a new address is generated, what should happen is indeed something like
  `recipient_ecdh_secret = HMAC-SHA256(key=blinding_derivation_key, msg=scriptPubkey_output)`
  as in Elements. But there are two minor questions:

  * Should we use some other function than HMAC-SHA256? For example, HMAC-SHA512 is a more natural choice because it's required in BIP32 anyway. But both Ledger and Trezor support HMAC-SHA256, so we could keep this compatible with Elements Core.
  * How is `blinding_derivation_key` computed? On hardware wallets, you probably want to minimize the amount of master secrets, so you could derive this again from the real master key, like `blinding_secret_key = HMAC-SHA256(key=real_master_key, msg="blinding")` or similar. Then you need to compute two derivations each time (probably doable but somewhat expensive). The advantage is that this is then compatible with the current Elements scheme and more importantly, the user can choose to store the blinding_secret_key on the host. And if doing HMAC-SHA256 twice is really to slow on the hardware wallet, we could still think about changing it to simple SHA256 or similar, which does not make a lot of difference.

Hi, is this description still up-to-date? I just found out about this thread, and the proposal for computing the blinding_derivation_key using a HMAC-SHA256 looks similar to what Libwally is doing now, which is inspired from this proposal, if I'm not mistaken. Since Green is using Libwally I assume that it computes the master blinding key like this when creating a new wallet? It sounds reasonable to me, are there downsides if we computed the master blinding key the same way in Elements?

I also think that the proposal for computing the sender_ecdh_secret in a deterministic manner instead of random makes sense too, but maybe there are issues that I don't think of?

stevenroose commented 4 years ago

Yeah I'm surprised to see this old thread around. I think we settled on suggesting SLIP-077 for deriving blinding keys. It's indeed what Green and libwally do.