Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add web3.eth.encrypt and web3.eth.decrypt functions to JSON-RPC #1098

Closed
wants to merge 8 commits into from

Conversation

topealabi
Copy link

@topealabi topealabi commented May 18, 2018

eip: 1024
title: Add web3.eth.encrypt and web3.eth.decrypt functions
author: Tope Alabi <alabitemitope@gmail.com>
status: Draft
type: Interface
created: 2018-05-14

Abstract

This EIP proposes a cross-client method for requesting encryption/decryption. This method will include a version parameter, so that different encryption methods can be added under the same name. Nacl is a cryptographically complete and well audited library that works well for this by implementers are free to choose their crypto. Ethereum keypairs should not be used directly for encryption, instead we should derive an encryption keypair from the account's private key for decryption and generate a random ephemeral keypair for encryption.

Parity wallet already implements a compatible [encrypt/decrypt] https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage method and the MetaMask version is on the way. Having a cross-client standard will enable a whole new wave of decentralized applications that will allow users to securely store their private data in public databases such as IPFS.

Motivation

Imagine an illegal immigrant named Martha. Martha moved to the United States illegally but then had 2 children there, so her children are citizens. One day Martha gets arrested and deported but her children get to stay. How will Martha pass power of Attorney, bank account info, identification docs, and other sensitive information to her children? Storing that data in a centralized database can be incriminating for Martha, so maybe decentralized databases like IPFS could help, but if the data is not encrypted anyone can see it, which kind of defeats the purpose. If Martha had access to a Dapp with end-to-end encryption connected to her identity, she could save her data in a decentralized, censor-proof database and still have confidence that only her children can access it.

More casually, Martha can create a treasure hunt game, or a decentralized chat app etc.

Specification


const nacl = require('tweetnacl')

/**
* Returns user's public Encryption key derived from privateKey Ethereum key
* @param {Account} receiver - The Ethereum account that will be recieving/decrypting the data
*/
web3.eth.getEncryptionPublicKey(receiver.address) { /* implementation */ }

/**
* Encrypts plain data.
* @param {string} encryptionPublicKey - The encryption public key of the receiver 
* @param {string} version - A unique string identifying the encryption strategy.
* @param {Bytes} data - The data to encrypt
* @param {Number} padding - The number of characters to left-pad the data with (optional)
* @param {Function} callback - The function to call back when decryption is complete.
*/ 
web3.eth.encrypt(encryptionPublicKey, version, data, padding, callback) { /* implementation */ }

/**
* Decrypts some encrypted data.
* @param {Account} receiver - The account that will decrypt the message
* @param {Object} encryptedData - Encrypted payload, the data to decrypt
* @param {Function} callback - The function to call back when decryption is complete.
*/
web3.eth.decrypt = function decrypt (recievier.privatekey, encryptedData, callback) { /* implementation */ }

To Encrypt:

  • Alice requests Bob's publicEncryptionKey
  • Bob generates his encryptionKeypair using nacl.box.keyPair.fromSecretKey(bob.ethereumPrivateKey)
  • Bob sends Alice his encryptionKeyPair.publicKey
  • Alice generates a random ephemeralKeyPair
  • Alice uses her ephemeralKeypair.secretKey and Bob's encryptionPublicKey to encrypt the data using nacl.box. She sends him an encrypted blob of the form:
{ version: 'x25519-xsalsa20-poly1305',
  nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej',
  ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=',
  ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }

To Decrypt:

  • Bob generates his encryptionPrivatekey using nacl.box.keyPair.fromSecretKey(bob.ethereumPrivateKey).secretKey
  • Bob passes his encryptionPrivateKey along with the encrypted blob to nacl.box.open(ciphertext, nonce, ephemPublicKey, myencryptionPrivatekey)

Rationale

These methods should require user confirmation. We include the versioning to allow different encryption/decryption types to be added under the same method name. For example, it might make sense to have a few kinds of decrypt methods, for different kinds of consent:

  • Consent to download a decrypted file.
  • Consent to return decrypted file to the current site.
  • Consent to return any number of decrypted messages to the current site over a certain period of time. (could enable chat apps)

Backwards Compatibility

Parity implements an encrypt/decrypt method with a different curve than the one which is intended in this proposal, but that it would be possible to add support for curves to this standard.
https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage

Test Cases

getEncryptionPublicKey(7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816) should return a public encryption key of the form "C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U="

web3.eth.encrypt("C5YMNdqE4kLgxQhJO1MfuQcHP5hjVSXzamzd/TxlR0U=", 'x25519-xsalsa20-poly1305-v1', {data: 'My name is Satoshi Buterin'}) should return a blob of the form { version: 'x25519-xsalsa20-poly1305', nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }

web3.eth.decrypt('7e5374ec2ef0d91761a6e72fdf8f6ac665519bfdf6da0a2329cf0d804514b816', { version: 'x25519-xsalsa20-poly1305', nonce: '1dvWO7uOnBnO7iNDJ9kO9pTasLuKNlej', ephemPublicKey: 'FBH1/pAEHOOW14Lu3FWkgV3qOEcuL78Zy+qW1RwzMXQ=', ciphertext: 'f8kBcl/NCyf3sybfbwAKk/np2Bzt9lRVkZejr6uh5FgnNlH/ic62DZzy' }) should return plain text/file of the form { data:'My name is Satoshi Buterin' }

Implementation

Parity wallet has already implemented a compatible encryption/decryption method. The Metamask version will be published soon.
https://github.com/topealabi/eth-sig-util/blob/master/index.js

Copyright

Copyright and related rights waived via CC0.

@wighawag
Copy link
Contributor

an encryption/decryption api is definitely needed. Did you see the discussion happening at #130

As mentioned there an origin field would provide security against malicious app requesting decryption , see #130 (comment)

@quantumproducer
Copy link

+1
I wrote https://github.com/QuantumProductions/eth-auth/blob/master/index.js as a wrapper for validating control of an address but a general purpose encryption API would be better

title: Add web3.eth.encrypt and web3.eth.decrypt functions
author: Tope Alabi <alabitemitope@gmail.com>
status: Draft
type: Interface Track
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no 'interface track'. Please use a type and category from EIP 0.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still not fixed.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type: Interface Track
type: Standards Track
category: Interface

Parity wallet already implements a compatible [encrypt/decrypt] https://wiki.parity.io/JSONRPC-parity-module#parity_decryptmessage method and the MetaMask version is on the way. Having a cross-client standard will enable a whole new wave of decentralized applications that will allow users to securely store their private data in public databases such as IPFS.

### Motivation
Imagine an illegal immigrant named Martha. Martha moved to the United States illegally but then had 2 children there, so her children are citizens. One day Martha gets arrested and deported but her children get to stay. How will Martha pass power of Attorney, bank account info, identification docs, and other sensitive information to her children? Storing that data in a centralized database can be incriminating for Martha, so maybe decentralized databases like IPFS could help, but if the data is not encrypted anyone can see it, which kind of defeats the purpose. If Martha had access to a Dapp with end-to-end encryption connected to her identity, she could save her data in a decentralized, censor-proof database and still have confidence that only her children can access it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This motivation is bound to piss people off but it gets big ups from me. Nice imagination. Just wanted to leave that here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. But it's also a very legit scenario.

@VoR0220
Copy link
Member

VoR0220 commented Jun 5, 2018

Isn't there something like this already available for Whisper? If not, this should be more finely integrated into Whisper, don't you think?

Copy link

@savil savil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! I had a question below:

/**
* Encrypts plain data.
* @param {string} encryptionPublicKey - The encryption public key of the reciever
* @param {string} version - A unique string identifying the encryption strategy.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to define an enum, and use it here instead of a raw string?

pros:

  • this would enable tools/IDEs to type-check any arguments here
  • makes it more discoverable which encryption-strategies are available e.g. via autocomplete

cons:

  • I don't know how easy or tough it would be to add new encryption-strategies i.e. new values to the enum, if the enum definition were to be included in the standard itself.
    • Maybe there's an alternate way of defining it?
    • e.g. if we could have a base-enum in the standard and implementations can "extend" it, but I'm not sure if the solidity language supports that. Oh, also this would only work for "string-enums", but solidity seems to only have "int-enums" https://solidity.readthedocs.io/en/develop/types.html#enums

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An enum would be a good idea when we have more than one option (as of now this is the only one).
Adding more options, and enumerating them, sound like a good thing for a future EIP that specifies this among its requires.


/**
* Returns user's public Encryption key derived from privateKey Ethereum key
* @param {Account} reciever - The Ethereum account that will be recieving/decrypting the data
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nitpick: reciever, is usually spelt as "receiver"

@coder5876
Copy link

coder5876 commented Jun 6, 2018

@topealabi This interface

web3.eth.getEncryptionPublicKey(reciever.privateKey)

cannot work this way since the dapp has not access to the users private key, this is more the lower-level implementation. The right interface IMO would be

web3.eth.getEncryptionPublicKey(reciever.address)

which would return the encryption public key corresponding to the private key associated to that address. This would require some sort of user interaction/UI to grant access to the private key, which would normally be encrypted/locked.

This line

@param {Object} data - The data to encrypt

is a bit unclear, since we are saying that we can encrypt any object? How to do that for a nested object for instance? Maybe it's better to just put bytes as the data to encrypt and let the dapp developer be in charge of serializing their specific data into this format?

Also in the MetaMask PR I mentioned padding and the security implications thereof. I don't think padding should be built into the encryption function, but it might be a good idea to expose a simple padding interface to remind developers that this is important to think about. Not really sure about this though, tweet-nacl does not include any padding functionality for instance.

@wighawag
Copy link
Contributor

wighawag commented Jun 7, 2018

@christianlundkvist re UI needed to get public key, It should not be necessary if the nonce of the account is greater than zero as the public key is already available on the network (using the private key here is only internal). Even if the nonce is still zero, for some signer (maybe metamask?) it could be assumed that the user is ready to reveal its public key in that context. (it will be hard to explain to the users what they are actually giving out and why does it requires their agreement)

For all, I looked briefly at the PR and did not see discussion related to the issue I mentionned in #130 regarding malicious dapp that ask the decryption of data published by another dapp. I proposed a solution using an 'origin' field to solve it.

See comment : #130 (comment) and the discussion that follows

@coder5876
Copy link

coder5876 commented Jun 8, 2018

@wighawag

public key is already available on the network

Not in this case. The public encryption key is on the curve curve25519 whereas the signing public key visible in the ETH transaction is on the curve secp256k1. So you’ll need to start with the private key to generate the public key.

Potentially you could also generate the public encryption key when a new account is created in the UI.

@geeogi
Copy link

geeogi commented Jun 10, 2018

Quick re-cap for anyone caching up:

We’d like to achieve end-to-end encrypted communications between ETH addresses. But, encryption requires a keypair that is specifically designed for the purpose – an ETH keypair won't do. Users will need a dedicated encryption keypair. Whichever way that encryption keypair is generated, the public-key will differ from the ETH address and must be broadcast between users prior to encrypted communication.

Some initial thoughts on this proposal so far:

This standard proposes generating an encryption keypair using the ETH private key, so that both key pairs (Ethereum & Encryption) use a common private key.

However, there are some risks perhaps in using the ETH private key for both the signing and the encryption of data:

  • Using a common private key focuses risk onto a single key: If a user’s Ethereum key is lost or compromised, then their encryption key will be too. Losing access to an ETH key would mean losing access to assets, personal data and private communications all in one go.

  • Possibly, patterns between a user’s signed data and their encrypted data could reveal something about the underlying common private key. This is despite the use of two different algorithms. I imagine this is unlikely but it seems possible at least (any cryptographers who can comment?).

  • Other cryptographic algorithms use key pairs which perhaps aren’t compatible with the use of an ETH private key. This standard could be at risk of discouraging or excluding these.

Most of these risks could be mitigated by some higher level protocol. But why not avoid these difficulties altogether and use separate keys? Any number of encryption keys can be associated with an individual using a digital signature from their ETH key pair. This standard could include an interface for key association which encourages the use of multiple keys – rather than run the risk of normalising the use of a single key.

@topealabi
Copy link
Author

topealabi commented Jun 11, 2018

Hi @geeogi , thanks for the recap. I Appreciate your concerns and I think this is exactly the kind of skepticism the ecosystem needs in order to make sure that we are doing good work. I’d be happy to address some of the points you’ve mentioned above. The main idea you seem to be teasing out is:

This standard proposes generating an encryption keypair using the ETH private key, so that both key pairs (Ethereum & Encryption) use a common private key.

That is incorrect. I think the confusion may be coming from the fact that the encryption keypair is derived from the Ethereum privatekey but let me explain how that works:

  • Signing and encryption use two different keypairs and two different curves. When the encryption keypair is derived from the Ethereum private key using curve25519, we end up with two different keypairs(private keys) that were generated using two different elliptical curves. This makes it nearly impossible to guess the Ethereum keypair by looking at an encrypted payload. That would be like someone guessing your Ethereum mnemonic seed phrase by looking at a signed Ethereum transaction.

Now if someone else gets a hold of your Ethereum private key, they can generate your encryption keys and control your assets but this concept is precisely what makes a decentralized world work. Please protect your private keys.

  • Because the keys are different, a user could generate and use their encryption keys as long as they want, even if they were to lose their Ethereum private key, the encryption keys would still work.

  • We have included a versioning interface which would allow future developers to include other crypto algorithms to this spec and possibly also specify which keypair to use in a multiple encryption-keypair scenario.

There are many benefits to having encryption keys as part of your digital identity, this EIP is a positive step towards a world where people can have private communications in public places without fear of censorship.

@wighawag
Copy link
Contributor

@christianlundkvist thanks for the correction. Sorry for not reading the spec carefuly.

But yes, we should allow signer to make it easier for users if the intention is to use such publick key with dapps. Since once it is used, it should be assumed to be public.

@geeogi
Copy link

geeogi commented Jun 11, 2018

@topealabi Thanks for your detailed response! I'm grateful to you for your work on this EIP so far.

I absolutely see the benefit in having encryption keys associated with your digital identity. This can be achieved now by digitally signing an encryption key pair using your ETH key.

As I understand from your explanation, using the ETH private key to generate the encryption key pair means that both key pairs will be vulnerable in the event that a single key (the ETH private key) gets compromised.

Granted, we aim to protect private keys at all times and this should always be encouraged. But i'm wondering what’s the benefit of using the ETH private key to generate the encryption key pair over say generating an encryption key pair with an entirely different seed?

@Arachnid
Copy link
Contributor

Why not use ENS to store the user's public key?

@topealabi
Copy link
Author

Hi @geeogi, thanks for the feedback. As we all know, the criteria for a keypair seed is that it is secret and unique, therefore Ethereum private keys are a good fit, with the added benefit that the user doesn’t have to memorize a new secret and the clients don’t have to drastically alter their architecture. It is true that if someone has your private key they could receive your packages but we could say the same for signing. As things stand today, if someone has your private key they can sign data packages as you, which is why we continue to emphasize that you protect your private key. We have structured this implementation in a way that actually makes it more difficult to guess your private key from an encrypted message than it would be to guess your private key from signed data.

With that said, we understand that all things evolve, and so we have created a version interface that allows developers to make further improvements to this protocol in the future.

@folsen
Copy link

folsen commented Jun 28, 2018

Just as a point of reference, Parity already has pretty much exactly this support in the parity_encryptMessage and parity_decryptMessage, we would just have to add a parameter to support curve selection for the curve that @topealabi wants to use. I think this would be an easy add for Parity and something we'd be willing to do.

@topealabi
Copy link
Author

@Arachnid

Why not use ENS to store the user's public key?

This is a great idea. This proposal tackles key-generation and allows each implementor to choose where they want to store their keys. I believe we can explore key storage as a future upgrade or a separate eip once we reach consensus on the key-generation bit.

@godfreyhobbs
Copy link

@geeogi What you are describing is known as the Dual Key Use which is part of Security Domain Separation.
I am confident that security experts would agree with the following:
Use a different key for different cryptographic operations, algorithms, and parameters.

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 16, 2022

i see; upon looking further, it looks like they implemented the same thing MetaMask did (and just deprecated). it's strange to see MetaMask and Ledger going in opposite directions on this.

I don't love the approach myself (any more than you do), but a low-impact route could be to just endorse it anyway (especially considering Ledger's recent adoption, etc.). though using the same key on different curves is obviously not great, i don't think there are any known attacks. i worry about developers at the application level; most of all the need is for something consistent which wallets will implement.

your approach sounds good too. i'm not an expert on how HD wallets work; but, given the need for the ETH account to serve as an identifier for the encryption public / private key, maybe we could use a "parallel" HD derivation path to the one which MetaMask (say) already uses, so that there's a one-to-one correspondence between signing keys and encryption keys. but an issue arises: what about the case where an account is "imported" into MetaMask, and so lies outside of the HD derivation chain? how would we generate an encryption keypair for that account?

unrelatedly: i don't see the point of using the ed25519 curve at all. couldn't we just use ECIES over secp256k1?

let me know your thoughts.

@TJKoury
Copy link

TJKoury commented Jul 16, 2022

I agree on keeping it to one curve, per the reasons given for deprecating this capability.

No address “lies outside” a derivation chain, the derivation is a one way function that can start at any private key. It’s not so much that two addresses are “unrelated” (since being able to prove they are actually unrelated would make the HD algorithm insecure), more that they are within a certain number of derivations. Also, importantly, If you give a counterparty an xpub key they can generate the next keys in the derivation path without needing any private keys.

Therefore, if we created a new BIP / EIP and used that number in the derivation path, even without creating a new algorithm for that specific proposal (copying the derivation function from one of the current heavily used purposes like BIP44 or 84) you could have a system that allows counter-parties to use a key generated from an “xpub” without needing to use a key previously used for signing purposes. The “worst case” scenario is having to do derivation and attempt decryption until it works; we might want to define a default header or append a small magic number/string to both reduce the amount of computation required to test and reduce the probability of a false positive. The best case is the “index” derivation path is pre-shared and from that point on the next key to be used happens in lockstep. This would also mean it would be possible to use unique keys every transmission, further increasing the security of the plan.

As part of the proposal we could define the “change” derivation address as “encryption”, since the concept of “change” does not apply to Ethereum. It would be interoperable with the current toolsets / libraries and require minimal changes to current applications.

Thoughts?

@MicahZoltu
Copy link
Contributor

a low-impact route could be to just endorse it anyway (especially considering Ledger's recent adoption, etc.)

When building an app, following a defacto standard can be useful. When authoring a standard, you SHOULD try to design the best standard possible and try (whenever possible) to not be constrained by what people are doing today. The best standards survive for decades/centuries, and no one cares at all about how people did things at the time the standard was created 100 years later, they care that the standard is good.

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 17, 2022

@TJKoury sure, in general i agree with your reasoning principles and will support this.

it's a bit subtle what xpub gives us. i assume you imagine that the "chain code" derivation data will only be shared directly with the person sending transmissions? it seems this is the only way we would get any meaningful forward secrecy (over and above using the same key every time). in any case, it seems that we would need the ability to spin up and export a fresh xpub for each person who wants to send us messages, yes? this seems beyond the API which MetaMask currently exposes (though certainly possible).

Therefore, if we created a new BIP / EIP and used that number in the derivation path

i agree with this.

No address “lies outside” a derivation chain, the derivation is a one way function that can start at any private key.

i'm not sure i was clear above. my thinking is that, in order to comply with the API, we need a way to associate, to each standard ETH account in MetaMask, a corresponding encryption public/private keypair. short of using the exact same keypair used for signing (which may be less secure—though I'm not personally convinced of this), the easiest way I see would be to use a "alternate" derivation path running in parallel to the main one which MetaMask implicitly uses when generating accounts. so if you request the encryption public/private key corresponding to the ith account in your MetaMask derivation chain, then it would return the public / private key of the ith element in the "parallel" derivation chain.

i'm not sure how this latter path would relate to the xpubs exported for the purposes of receiving repeated communications. presumably it would be separate from those.

@TJKoury
Copy link

TJKoury commented Jul 17, 2022

it's a bit subtle what xpub gives us.

The xpub (and ypub, zpub) allows you to derive the public keys for a derivation path without the private key. If you give an xpub to another party, they can generate all public keys for all keypairs derived from the starting keypair. One of the original use cases was to put the xpub on a server for an online store, and let the customers get a new address to send payments to for every checkout, so as to not reuse addresses.

easiest way I see would be to use a "alternate" derivation path running in parallel to the main one which MetaMask implicitly uses when generating accounts

We could, thus the new BIP/EIP I proposed, however the "easiest" way we could do it currently is as I mentioned, simply use the "change" address as "encryption". This would be against convention for UTXO-based cryptos but Ethereum has no need for a "change" address.

So, for HD derivation path "m/44'/60'/0'/0'/0" the encryption keypair could be generated by using the derivation path "m/44'/60'/0'/1'/0".

We could write this convention into an EIP and create a very simple drop-in solution that works with all current HD wallet implementations. I have not seen any Ethereum wallet actually use the "change" address in HD derivation since it has no use, so this would not conflict with any known implementation.

@firnprotocol
Copy link
Contributor

right. i think your proposal is compatible with what i was (attempting to) say.

shall we proceed? :)

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 17, 2022

The xpub (and ypub, zpub) allows you to derive the public keys for a derivation path without the private key.

i understand the process mechanically; the question is, as far as encryption is concerned, if i'm sending you multiple messages, then is encrypting them under different pubkeys (all within an xpub path) more secure than just using the same public key every time? it's not obvious why this would be. contrast this with the case of bitcoin, where the TXO addresses obviously need to be made public, so there is an (at least mild) privacy / linkability benefit to using different keys. whereas in the case of encryption, i can send encrypted messages to you in a public channel without ever having to publish the corresponding public keys, so the linkability benefit of xpub over and above reuse wouldn't exist.

there could be a forward secrecy benefit to using xpub for encryption if you (the recipient) could somehow delete each early key in the derivation path after it's used to decrypt its corresponding message, including all relevant chaincodes. but this is getting fairly complicated.

@TJKoury
Copy link

TJKoury commented Jul 17, 2022

No, I don’t believe it would be more secure cryptographically, and obviously if you’re sharing the xpub over public channels, it negates any sort of fundamental operational benefit.

The scheme you mention at the end is something that I have considered for a while; if you have derivation paths for signing and encryption keys, and share xpub keys, you could verify a signature of the encrypted data (since you can get recover the public key from the signature), then use the next public key in the derivation path for forward encryption, “burning” previous key paths.

If both parties keep the xpubs protected after initial secure key exchange (say on a standalone system) and airgap the following sets of key pairs, you could have an extremely robust system that could survive even the compromise of operationally used private keys.

It is fairly complicated as you state, and I would not recommend building this type of scheme into a standard or hardcode its use into a wallet. At the same time, being aware of the possibilities and engineering a solution that can take advantage of them is something to consider.

@TJKoury
Copy link

TJKoury commented Jul 17, 2022

And yes, we should proceed. I would be happy to collaborate.

@kdenhartog
Copy link
Contributor

Just reading through the thread and responding to a few things:

but a low-impact route could be to just endorse it anyway (especially considering Ledger's recent adoption, etc.). though using the same key on different curves is obviously not great, i don't think there are any known attacks

This seems like a high risk chance to take for wallets that are storing and managing private keys that handle billions of dollars in conglomerate to leave it in now that we know it's not provably secure. I certainly don't want to be on the hook for accepting that risk when we can design something safer.

unrelatedly: i don't see the point of using the ed25519 curve at all. couldn't we just use ECIES over secp256k1?

I agree on keeping it to one curve, per the reasons given for deprecating this capability.

I believe the reason Ed25519 was chosen was because it was much easier to prevent issues across a variety of wallets because the box() APIs were considered higher level APIs that were harder to misuse, are implemented in a variety of languages, have been reviewed extensively, and were more common by the authors who worked on this. Additionally, based on the second paper I linked above ECIES and ECDSA can maintain joint security together in a provably secure way, but only under the Generic Group Model (GGM) where as if the keys are separated ECIES would maintain security under the GGM model and the Random Oracle Model (ROM). So we see that there's a degradation of security when relying on the joint security model which suggests unless absolutely necessary (like in EMVs case where they could only store a single key on the cards) then we probably shouldn't take this path.

Also, importantly, If you give a counterparty an xpub key they can generate the next keys in the derivation path without needing any private keys.

Wouldn't that allow the counterparty the ability to derive all of the encryption keys below the provided path? That seems like a solution that leads to fingerprinting privacy problems which are a major consideration for browsers now and I suspect will become a major consideration of wallets going forward in web3. I don't think this is a feature we want to support since it means a user can be tracked by their encryption key unless they generate a separate seed for each party they want to interact with since the counter parties could coordinate and brute force check if the user is the same person across dApps. In this case, key sharing being done by the wallet becomes a privacy enhancing feature rather than a bug in my opinion.

The best case is the “index” derivation path is pre-shared and from that point on the next key to be used happens in lockstep. This would also mean it would be possible to use unique keys every transmission, further increasing the security of the plan.

How would this be coordinated across parties?

the easiest way I see would be to use a "alternate" derivation path running in parallel to the main one which MetaMask implicitly uses when generating accounts. so if you request the encryption public/private key corresponding to the ith account in your MetaMask derivation chain, then it would return the public / private key of the ith element in the "parallel" derivation chain.

There's an implicit assumption here that the wallet needs to be avoid managing state of signing and encryption keys and as such the signing public key (e.g. ETH account) should act as an identifier for the encryption key in a way that couples the pair together even though they don't need to be. What's the benefit in trying to keep these key pairs coupled when they don't need to be?

So, for HD derivation path "m/44'/60'/0'/0'/0" the encryption keypair could be generated by using the derivation path "m/44'/60'/0'/1'/0".

This runs into that privacy problem I was talking about. While it is a simple solution, I don't think we should be ignoring these fingerprinting concerns going forward.

i understand the process mechanically; the question is, as far as encryption is concerned, if i'm sending you multiple messages, then is encrypting them under different pubkeys (all within an xpub path) more secure than just using the same public key every time?

Yes, this is a difference between weak perfect forward secrecy and strong perfect forward secrecy because compromise of the same public key on the recipient side means that the attacker is capable of decrypting all previous messages that they passively intercepted. It should be noted that the HD path mechanism isn't achieving strong PFS either because if the seed is compromised then the attacker can again decrypt all possible passively intercepted messages because they can regenerate all of the recipient private keys. A simple HD scheme can't get you post compromise security like Signal is able to achieve.


Reading through this thread so far I think there's a lot of baked in requirements and assumptions that aren't clear to me why they exist. I think before we dive into any sort of solution design we should figure out what it is that we actually need going forward. As @MicahZoltu pointed out we should really be designing this for something that can last or we'll be back at this stage in a few years time. There's a lot we can learn from evaluating the usage of these APIs over the past 5 years, what limits dApps ran into, what limits wallets ran into and from there adjust our expectations of what this is trying to achieve accordingly.

For example, I know one implicit assumption that everyone has made here from the outset of this EIP is that the key generation MUST rely on an HD scheme which is used to derive all keys from a seed phrase. Why is that? Historically speaking, it comes from the BTC motivation of "not your keys not your crypto" which has an implicit tradeoff around key recovery by making everything seed based. This isn't a necessary assumption though, it's just something that has stuck because that's how everyone else did it.

If we're already revisiting assumptions here why not consider refactoring that as well? It greatly simplifies the security model if we can independently generate the private keys for each interaction which seems like a really good idea considering these wallets are collectively managing hundreds of billions of dollars worth of assets and one incorrect design choice can become incredibly costly here. On the flip side, the two downsides that comes to my mind with this design consideration are:

  1. it would require a greater refactor to wallets which may not be adopted
  2. it requires greater state management which has to be coordinated if the user wants to migrate from one wallet to another

These may be reasons to not consider it as a solution, but until we decide on what the users needs are, what the wallet vendors needs are, what the dApp developers needs are, and what the implementation costs are I don't think it's a smart choice for us to just dive in and find a solution without considering what's actually useful.

@TJKoury
Copy link

TJKoury commented Jul 18, 2022

Wouldn't that allow the counterparty the ability to derive all of the encryption keys below the provided path?

Yes, in some cases this is a feature, not a bug. There are many PKI implementations where attribution is required; in these cases the ability to attribute an address to an owner in as few steps as possible is a benefit.

The fact that this is possible, but not required, is a positive for using HD derivation.

This runs into that privacy problem I was talking about. While it is a simple solution, I don't think we should be ignoring these fingerprinting concerns going forward.

This is only an issue if the xpub is shared, which it does not have to be. Even knowing a wallet is using this relational derivation path is mathematically useless without knowing the chain code.

For example, I know one implicit assumption that everyone has made here from the outset of this EIP is that the key generation MUST rely on an HD scheme which is used to derive all keys from a seed phrase. Why is that?

HD derivation does not rely on using a seed phrase, you are confusing BIP39 (seed phrase) with BIP32/44/84 (HD derivation). They are used in combination due to ease of use and compatibility. It’s the same reason almost all wallets use only 128 bits of entropy (12 words) rather than up to 24 words for the full 256 bits, which is exponentially more secure.

If there is another option which works better I am sure everyone will consider it.

A simple HD scheme can't get you post compromise security like Signal is able to achieve.

I don’t think anyone is arguing that it would by itself, however it does give a good starting point compatible with current wallet implementations, and does not preclude finding a scheme that would provide it. PFS using ephemeral DH key exchange in the Open Whisper protocol only works if you disable logging for example, which is an implementation detail. You could easily generate and then encrypt ephemeral keys with / for an HD derived key, which would provide PFS even if the HD private key was compromised.

We need to keep in mind that the primary purpose of these wallets is maintaining access to value as you say:

storing and managing private keys that handle billions of dollars in conglomerate

If a seed phrase is compromised, the damage is chiefly the loss of funds. Trying to rewrite the whole playbook at this juncture for a single feature, ignoring prior art, seems much riskier than using current tools to accomplish the job.

@kdenhartog
Copy link
Contributor

kdenhartog commented Jul 18, 2022

Yes, in some cases this is a feature, not a bug.

From who's perspective, a privacy conscious user or a dApp developer? Which persons considerations are more important? This is why I'm trying to start the discussion as a framing of requirements and priority tradeoffs rather than jumping strictly into a solution driven conversation. This is the tradeoff of writing standards. They simply take longer than just agile engineering development. And since this API is meant to be implemented across a variety of different wallets in an interoperable way it's wise of us to figure out what the goals are here first and follow more of a standards based approach for solving this problem rather just finding something that works.

If there is another option which works better I am sure everyone will consider it.

The generate independent keys idea above is another option, but until we determine the goals it's too hard to determine what "better" is since that's a subjective view point. Your points raised are valid trade off considerations and are not something I'm attempting to write off as unimportant. That's why I wanted to call it out as a design tradeoff in my comment above.

We need to keep in mind that the primary purpose of these wallets is maintaining access to value

Is it still if we're adding secure messaging platform APIs on top? Seems like they're becoming more than that with these APIs.

EDIT: Also, I do think discussing these different solutions is useful because it grounds the discussion about what's needed with the discussion about what's possible.

@TJKoury
Copy link

TJKoury commented Jul 18, 2022

Is it still if we're adding secure messaging platform APIs on top? Seems like they're becoming more than that with these APIs.

Yes, it is. If we attempt to create a solution that makes it more difficult to do the basics, or creates such an development upheaval that no one implements it, then we have failed in our objective.

@kdenhartog
Copy link
Contributor

+1 you won't find me disagreeing with that at all. Equally so we could find dApp devs not willing to use it because it doesn't meet their needs (e.g. there's a lot of dApps out there which don't rely on this functionality. I don't know why that is), or users choosing not to use a particular wallet because each wallet is doing it differently and only the popular wallet supports the trendy dApp.

@kdenhartog
Copy link
Contributor

kdenhartog commented Jul 18, 2022

In my mind here's the requirements worth highlighting so far:

  1. Must be secure (TBD what secure means e.g. PCS necessary or is wPFS good enough?)
  2. Should be simple to implement (this probably throws my idea out TBH)
  3. Should not create a fingerprinting issue for users

open questions:

  1. Should this allow for secure messaging protocol between wallet peers or primarily operate as a client-server communication channel?
  2. Should this account for group messaging at scale like MLS does?
  3. How should public key discovery occur?
  4. Should public key discovery be attached to the ETH account model?
  5. Do messages need to be able to migrate from one wallet to another?
  6. How do we want to manage key management across different wallets?
  7. What's the adversary model that requires additional message security beyond TLS?
  8. What's the primary and secondary use cases being addressed by this platform API?
  9. Do we want the API to operate as a high level API or low level API?

What ones can you come up with @TJKoury to add to that?

@awoie
Copy link
Contributor

awoie commented Jul 18, 2022

imo, the following use cases might be low-hanging fruits and would be already great:

  • as a dapp developer i want to encrypt data to a connected eth user, so that the eth user can decrypt the data with their private key.
  • as a eth wallet user i want to encrypt data to my private key, so that i can store the data securely in a decentralized or self-sovereign storage solution and so i can decrypt the data later.
// encryption
const encryptionKey = web3.eth.getPublicEncryptionKey(address, opt)
const jwe = JWE.encrypt(encryptionKey, data, opt)

// decryption
// JWE specific steps ...
const data = web3.eth.decrypt(???, adress, opt)
// JWE specific steps ...

a bit more hard to achieve and probably requires an extra layer (discovery protocol):

  • as a eth wallet user i want to encrypt data to another eth wallet user, so i can share a link to an encrypted object with that user and that user can decrypt the data with their private key.
  • as a eth wallet user i want to send an encrypted message to another eth wallet user with perfect forward secrecy.

if discovery is part of the solution then it would be great if the eth account would act as the root identifier:

const publicKey = web3.eth.getPublicEncryptionKey(recipientAccount, opt)

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 18, 2022

What's the benefit in trying to keep these key pairs coupled when they don't need to be?

the idea here is that (at least as things currently are) the eth account serves as a "handle" onto the encryption / decryption keys, as well as onto signing behavior. dissociating these could make things harder to keep track of, but could definitely be done.

For example, I know one implicit assumption that everyone has made here from the outset of this EIP is that the key generation MUST rely on an HD scheme which is used to derive all keys from a seed phrase. Why is that? Historically speaking, it comes from the BTC motivation of "not your keys not your crypto" which has an implicit tradeoff around key recovery by making everything seed based. This isn't a necessary assumption though, it's just something that has stuck because that's how everyone else did it.

in my view, this assumption is actually crucial. many applications—mine, as well as e.g. Tornado cash—use encryption / decryption precisely so that management of new, application-level secrets can be bootstrapped onto the same seed phrase people have already gone through the trouble of storing (by encrypting these new keys using MetaMask and storing them in reliable storage). if you break this assumption, then (in these particular applications) you lose the whole point of using encryption / decryption in the first place, since by the time you're making the user memorize / store / manage new decryption keys, you might as well just make them store / manage the new application-level keys directly, bypassing encryption entirely. edit this point is only relevant if the management of the decryption keys has to be handled by the user, as opposed to the wallet; i guess i should have clarified this. but even if the wallet does handle it, this makes export / transfer of wallets more complex.

It greatly simplifies the security model if we can independently generate the private keys for each interaction

right, my issue is not so much whether the keys are independent from the existing signing keys, as whether they can be derived from the same seed phrase. i think this latter assumption is what's important.

@firnprotocol
Copy link
Contributor

Wouldn't that allow the counterparty the ability to derive all of the encryption keys below the provided path? That seems like a solution that leads to fingerprinting privacy problems which are a major consideration for browsers now and I suspect will become a major consideration of wallets going forward in web3.

This runs into that privacy problem I was talking about. While it is a simple solution, I don't think we should be ignoring these fingerprinting concerns going forward.

as @TJKoury mentions, there are only very restricted circumstances in which someone would be able to derive subsequent keys. in short, it's possible only when an "xpub" is used and the counterparty is in possession of the relevant chaincode. in this circumstance, the ability to derive future keys is more likely a feature than a bug. i suppose the idea's that in practice, the receiver of messages will share the xpub (including chaincode) directly with the sender (i.e., through a private channel). so that only the sender will be able to do this derivation subsequently, but not anyone else. (a bit of a bootstrapping issue how this channel is to be come upon, but we can assume they're able to do a one-time advance exchange of the secret material.)

@kdenhartog
Copy link
Contributor

if you break this assumption, then (in these particular applications) you lose the whole point of using encryption / decryption in the first place, since by the time you're making the user memorize / store / manage new decryption keys, you might as well just make them store / manage the new application-level keys directly, bypassing encryption entirely.

Can you explain this a bit further because I'm not following this assumption? For example, password managers leverage a password plus a KDF in order to store any number of arbitrary secrets which are randomly generated such as a randomly generated password. They don't require the user to store or manage everything instead the password manager handles both of those things for the user. It could just as easily be used to generate an arbitrary key, store the key, and also help the user to supply it to thee web app when necessary and call it a wallet. So with that in mind I'm not following what additional benefits are gained by using the most common model for wallets rather than the most common model for password managers. The difference here is that wallets today require only the seed as managed state in order to regenerate everything again rather than requiring an import/export functionality like password managers have. However, as soon as you need to import and export messages to migrate wallets you lose the ability to regenerate everything from the seed, so it's none of the upsides with all the downsides in my eyes. Hence why I asked if that's necessary which from a UX perspective I would think it would be for both dApp devs and users.

as @TJKoury mentions, there are only very restricted circumstances in which someone would be able to derive subsequent keys. in short, it's possible only when an "xpub" is used and the counterparty is in possession of the relevant chaincode. in this circumstance, the ability to derive future keys is more likely a feature than a bug. i suppose the idea's that in practice, the receiver of messages will share the xpub (including chaincode) directly with the sender (i.e., through a private channel). so that only the sender will be able to do this derivation subsequently, but not anyone else. (a bit of a bootstrapping issue how this channel is to be come upon, but we can assume they're able to do a one-time advance exchange of the secret material.)

Two things to clarify here - This xpub based public key derivation is only possible through secp256k1 derivation schemes right? Isn't there a downside to leveraging secp256k1 for key agreement schemes (aka deriving encryption keys) because this curve is susceptible to twist attacks that can reveal the private key with encryption? I've actually found that this was a vulnerability for a dependency that many wallets relied on.

Why not pick a different curve here then in order to avoid this concern altogether?

Also, just to clarify so that I'm not making a mistake in my understanding of the way this HD scheme works, who's sharing the chaincode and when? Is the user of the wallet because my concern here is around the user sharing the pub key+chaincode with dApp 1 and then sharing the child key of that pubkey+chaincode with dApp 2 and dApp 1 would be able to coordinate even if signing was never used.

Obviously if transactions from the associated accounts are used then that data is public and it's possible to see which contracts are being interacted with to easily detect this usage. However, encryption should probably make slightly different assumptions since it's possible that a dApp may want to only use a encryption key without wanting to do signing/on-chain transactions.

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 19, 2022

So with that in mind I'm not following what additional benefits are gained by using the most common model for wallets rather than the most common model for password managers.

right, I'm not opposed to the password manager approach in principle—but then the burden would fall on us to define the import / export protocol. note also that password managers (at least LastPass) routinely sync all passwords with a remote server, so that import / export is less critical; this is obviously not a desirable design choice in the case of crypto wallets.

They don't require the user to store or manage everything instead the password manager handles both of those things for the user.

right; this was my mistake; see the "edit" at the end of my second paragraph above.

However, as soon as you need to import and export messages to migrate wallets you lose the ability to regenerate everything from the seed, so it's none of the upsides with all the downsides in my eyes.

i'm not sure I follow this. why would you lose the ability? in my mental model of things, the relevant ciphertexts here are being posted somewhere durable (e.g., on-chain). so as long as you can recover the decryption keys from the seed, you can also recover the messages. even if they're not posted durably in the case of messaging, they definitely are in the case of application-key-bootstrap (e.g. Tornado), so there are still cases where you would indeed get the benefit of recovery.

Isn't there a downside to leveraging secp256k1 for key agreement schemes (aka deriving encryption keys) because this curve is susceptible to twist attacks that can reveal the private key with encryption?

was not aware of this; but if you read into it, it looks like this relies on the victim not checking whether the attacker's ephemeral point is on the curve:

If Bob has a naive implementation of the elliptic curve multiplication (Q = b*P) that does not check if P is actually on the secp256k1 curve (which it isn't) then his software will instead perform the computation on the curve E2, and Alice can perform the small subgroup attack described above.

this is only an issue when encrypting to others (and not in the encrypt-to-self paradigm Tornado uses), and is easy to prevent if you check that the incoming point is on the curve. moreover this is only an issue if the attacker sends an uncompressed point; most implementations these days expect compressed points. most importantly, it looks like this is only relevant if the attacker can convince the victim to use the same (sensitive) exponent across multiple encryptions. but this won't happen for ECIES; indeed, the encryptor (who would be the victim here) uses a fresh random ephemeral key in the DH exchange, not a persistent key, and the attacker gains nothing by learning about this ephemeral secret key.

so for this to be an issue, a lot of things have to go wrong. in particular, if we define eth_decrypt to assume ECIES encryption, then will be essentially impossible to carry out this attack, unless the victim has multiple cryptographic bugs in their code. that's my read at least.

Why not pick a different curve here then in order to avoid this concern altogether?

this sort of thing will probably be possible for any Weierstrass curve (leaving basically ec25519, which was already ruled out above) and, in any case, the risk is so low with correctly implemented code that i wouldn't find that path justified.

Also, just to clarify so that I'm not making a mistake in my understanding of the way this HD scheme works, who's sharing the chaincode and when?

as i understand it, if i want to receive a stream of messages from you, then I will generate a new "xpub" (i.e., a public key + chaincode), then send both directly to you (though some assumed existing private channel). i will never reuse that chaincode with anyone else. thus your scenario with dApp1 and dApp2 just wouldn't happen (at least under typical use patterns).

now whether this is actually any better / more secure than me just sending you a single key is much less clear to me. i think that'd only be possible if i could generate an xpub whose chaincode was outside of my HD chain, and delete old elements of the xpub sequence after i use them to decrypt. in this way you could get some sort of forward secrecy. but i'm not sure whether this is in scope, because it would require xpubs whose chaincode lived outside of the HD chain.

@kdenhartog
Copy link
Contributor

this is obviously not a desirable design choice in the case of crypto wallets.

I disagree, that sounds like a great recovery and cross device scheme to me to be able to backup encrypted copies either to a service offered by the wallet or by downloading it and storing it yourself. This is the same direction webauthn is looking to head with passkey as well, so it's a scalable and secure design that's looking to be deployed to billions of people it seems like Additionally, you could build in a SSS scheme on top based on the KDF digest which allows for social key recovery without the backup service ever gaining access to keys. I do understand the scepticism here, but key backup via cloud or personal servers seems like a great way to solve the lost seed problems of today.

the relevant ciphertexts here are being posted somewhere durable (e.g., on-chain). so as long as you can recover the decryption keys from the seed, you can also recover the messages. even if they're not posted durably in the case of messaging, they definitely are in the case of application-key-bootstrap (e.g. Tornado), so there are still cases where you would indeed get the benefit of recovery.

Ahh that sounds like a big no-no. On chain costs gas for storage and runs a risk of passive compromise attacks in the future. Same issue if they're published to IPFS with out an additional authorization layer above. Additionally in the case of "application-key-bootstrap" that's managed by the user (e.g. download the key) so the burden is on them to manage migration. This is similar to import and export of plaintext CSV files for password managers though so is certainly plausible as a simple import/export protocol. However as soon as you start factoring in being able to move messages from one wallet to another. Even just the URL of where they exist in a "durable location" you have to factor in storing those URLs as well. So why not just do the same thing for both messages and keys as soon as you've already built the protocol for messages?

this is only an issue when encrypting to others (and not in the encrypt-to-self paradigm Tornado uses), and is easy to prevent if you check that the incoming point is on the curve.

This is true, but crypto is hard. (That vulnerability wasn't discovered in an open source wallet that's used by 8.2 million repos) So you can trivialize it, but it remains a problem that can be designed around by simply picking a different curve. Why risk it?

encrypt-to-self paradigm Tornado

Also, I'm not finding in Tornado's codebase or the docs where they're doing this encrypt-to-self. Do you by chance know where the code is so I can take a look at their implementation to understand how it's being used? I presumed based on Metamask's medium article that this API was in use, but it wasn't clear to me which dApps were actually using it. From what I could tell the file being downloaded was for their Pedersen commitment scheme not for encryption.

which was already ruled out above

It was ruled out to reuse the same key material across curves, not to rule out the curve all together I thought. The only other thing I saw on the topic is that @TJKoury would prefer to reuse the same curve, but that's where we encounter real design problems that have been discovered in the wild. Let's not trivialize implementing crypto here that's been shown to have mistakes and took a bit to catch for the sake of preference.

if i want to receive a stream of messages from you, then I will generate a new "xpub" (i.e., a public key + chaincode), then send both directly to you

Thanks for the further clarification. And is the publicKey and chain code unique to each dApp or just one of these? From my read of things the public extended key isn't that way and so if it got reused across dApps this fingerprinting would be a concern even if different indexes within the path are used because the higher up path could correlate the lower path assuming they're coordinating.

(though some assumed existing private channel)

Yeah this is why I was going down the route of What's the adversary model that requires additional message security beyond TLS? since it's presumed the existing private channel would be a TLS channel is secure enough to pass private messages on so what's the additional encryption needed for?

@kdenhartog
Copy link
Contributor

@awoie

as a dapp developer i want to encrypt data to a connected eth user, so that the eth user can decrypt the data with their private key.

Couldn't this be covered by the TLS channel setup when the user connects to the dApp? In DIDComm it's not assumed there's a client-server channel, but as far as I can tell dApps still assume this architecture. The one exception to this was Augur which operates as a stateless SPA that's hosted, but since it's stateless it couldn't generate a key to encrypt it to the user plus it's already being ran client side so I don't think that use case aligns.

as a eth wallet user i want to encrypt data to my private key, so that i can store the data securely in a decentralized or self-sovereign storage solution and so i can decrypt the data later.

This one doesn't seem like it would require the eth account to handle it. In fact, it's better to just use a symmetrical key in this case since the eth user is encrypting it for themselves.

a bit more hard to achieve and probably requires an extra layer (discovery protocol):

as a eth wallet user i want to encrypt data to another eth wallet user, so i can share a link to an encrypted object with that user and that user can decrypt the data with their private key.

as a eth wallet user i want to send an encrypted message to another eth wallet user with perfect forward secrecy.

These get to the heart of one of the questions I'm curious about as well. Is this platform API meant only for developers or also for p2p communication between ETH users? If this is the case, there's quite a few different assumptions we'd need to change from the original eth_getEncryptionKey and eth_decrypt API design. I'm up for it, but I'm not sure there's consensus among wallet devs for that. Spruce has quite a few contacts with wallet devs already. Would you be able to let them know about this thread and see if they wouldn't mind popping into this thread and weighing in? Either that or I'm happy to setup a call for this and see who's on board with what. Personally I think a wallet dev call to discuss interoperable wallet platform APIs beyond the scope of just these ones would be incredibly useful!

@firnprotocol
Copy link
Contributor

firnprotocol commented Jul 19, 2022

I disagree, that sounds like a great recovery and cross device scheme to me to be able to backup encrypted copies either to a service offered by the wallet or by downloading it and storing it yourself.

in the case of Ledger wallets, this might not even be possible, since the secret keys can never leave the device or be exported, by design. so it seems that this would rule out compatibility with Ledgers.

Additionally in the case of "application-key-bootstrap" that's managed by the user (e.g. download the key) so the burden is on them to manage migration.

i was perhaps not clear. by "application-key-bootstrap", i mean "encrypt a new, application-level secret key under an encryption key whose decryption key derives from the wallet's seed phrase". so the point is precisely to not place any additional burden on the user to either download this application-level key or take any additional steps upon migration.

Ahh that sounds like a big no-no. On chain costs gas for storage and runs a risk of passive compromise attacks in the future.

i beg to differ 😄 like it or not, this is exactly what Tornado cash—a very widely used and trusted app—does. they've apparently found that these "risks" are outweighed by the key-management benefits of not forcing users to download and maintain new sporadic keys.

Also, I'm not finding in Tornado's codebase or the docs where they're doing this encrypt-to-self. Do you by chance know where the code is so I can take a look at their implementation to understand how it's being used?

i'm not familiar with their codebase, but see attached screenshot for proof. you can also experiment with their app.

Screen Shot 2022-07-18 at 10 52 58 PM

This is true, but crypto is hard. (That vulnerability wasn't discovered in an open source wallet that's used by 8.2 million repos) So you can trivialize it, but it remains a problem that can be designed around by simply picking a different curve. Why risk it?

i don't know if i was emphatic enough above in explaining why this isn't a risk to us. it's an attack on ECDH, not ECIES. this is only a risk if (1) the victim Bob doesn't check that the attacker Alice's public key is on the curve and (2) the victim Bob exponentiates some point supplied by the attacker Alice by the same sensitive secret scalar multiple times. but this will never happen with ECIES, by definition of the scheme, since Bob doesn't use any persistent secret key whatsoever, but rather only exponentiates Alice's points by random scalars. so Bob (the encryptor) has no secret key for Alice to crack.

@awoie
Copy link
Contributor

awoie commented Jul 19, 2022

@awoie

as a dapp developer i want to encrypt data to a connected eth user, so that the eth user can decrypt the data with their private key.

Couldn't this be covered by the TLS channel setup when the user connects to the dApp? In DIDComm it's not assumed there's a client-server channel, but as far as I can tell dApps still assume this architecture. The one exception to this was Augur which operates as a stateless SPA that's hosted, but since it's stateless it couldn't generate a key to encrypt it to the user plus it's already being ran client side so I don't think that use case aligns.

"connected" in this context means -> connect button, not didcomm. the eth user is just using a SPA and clicks on "connect". this just means, the SPA knows the eth user's address (account) and nothing else (also no authentication). the "connect" button is what almost all dapps have implemented. typically, web3modal or rainbowkit's modal would then be displayed to select the wallet. to store personal data about the end user, this data should be encrypted to the user's private key. the dapp (or the browser, perhaps through extensions) can provide the user with options how to store the data on the user's behalf, e.g., on ceramic, filecoin, kepler, dwn etc.

Update:
private key doesn't mean the secp256k1 key since i also have some reservations regarding doing ECDH-ES with secp256k1 keys and especially we shouldn't use the same key for eth signing and encryption.

as a eth wallet user i want to encrypt data to my private key, so that i can store the data securely in a decentralized or self-sovereign storage solution and so i can decrypt the data later.

This one doesn't seem like it would require the eth account to handle it. In fact, it's better to just use a symmetrical key in this case since the eth user is encrypting it for themselves.

this could be done by symmetrical encryption, but you don't want to send all your data through a walletconnect session for example. so, you would rather create a symmetric key in the SPA to encrypt the data and then encrypt the symmetric key with ECDH via the wallet.

a bit more hard to achieve and probably requires an extra layer (discovery protocol):
as a eth wallet user i want to encrypt data to another eth wallet user, so i can share a link to an encrypted object with that user and that user can decrypt the data with their private key.
as a eth wallet user i want to send an encrypted message to another eth wallet user with perfect forward secrecy.

These get to the heart of one of the questions I'm curious about as well. Is this platform API meant only for developers or also for p2p communication between ETH users? If this is the case, there's quite a few different assumptions we'd need to change from the original eth_getEncryptionKey and eth_decrypt API design. I'm up for it, but I'm not sure there's consensus among wallet devs for that. Spruce has quite a few contacts with wallet devs already. Would you be able to let them know about this thread and see if they wouldn't mind popping into this thread and weighing in? Either that or I'm happy to setup a call for this and see who's on board with what. Personally I think a wallet dev call to discuss interoperable wallet platform APIs beyond the scope of just these ones would be incredibly useful!

@obstropolos ^^

@ahmaddradii
Copy link

Are there alternative solutions to dispense with the encryption and decryption of Metamask ?

@firnprotocol
Copy link
Contributor

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

Successfully merging this pull request may close these issues.

None yet